# NetX Web 前端规范 ## 技术栈 - React 19 + TypeScript + Vite - React Router(工作台 + 模块页) - TanStack Query(服务端状态) - 自研 i18n(`src/i18n/`,无额外依赖) ## 代码结构 **按领域(feature)组织,依赖方向 `features → 共享层`,反向禁止**(ESLint `no-restricted-imports` 强制)。 ``` src/ main.tsx App.tsx # 入口、路由表(lazy 引入各 feature 页面) features/<领域>/ # 业务模块,kebab-case 目录名 XxxPage.tsx # 路由入口(XxxLayout.tsx 为带子路由的壳) components/ # 该领域私有组件(含 modals/) hooks/ # 该领域私有 hooks(useXxx.ts) lib/ # 纯逻辑(无 React),可单测 layout/ # 应用壳:AppLayout、顶栏、ErrorBoundary components/ (+ ui/) # 跨领域复用 UI(无业务 API 调用) hooks/ utils/ # 跨领域 hooks / 纯工具 services/api/ # 后端 API,按领域分文件,index.ts 汇总导出 types/ # 后端契约类型,按领域分文件,index.ts 汇总导出 i18n/locales/{zh,en}/.ts # 文案,一个命名空间一个文件 config/ # 导航与模块注册表(modules / networkNav / auditNav) constants/queryKeys.ts # React Query key styles/ # 见「样式规范」 ``` 现有领域:`auth` `workbench` `managed-ne` `ume` `collect` `config-sync` `webcrt` `topology` `network` `port-traffic` `cutover`(业务状态/对比/割接) `audit` `users` `api-tokens` `sessions`。 约定: - 只被**一个**领域用到的代码放进该领域的 `components/ hooks/ lib/`;被**两个以上**领域用到才上移到共享层。 - 跨领域只引用对方的页面级导出或 `components/ lib/` 下的文件;现有合理依赖:`* → auth`、`topology/ume → managed-ne`。 - 共享层(`components hooks utils services i18n types constants`)**不得** import `features/`。 - 新增 API:写进 `services/api/<领域>.ts`;新增类型:写进 `types/<领域>.ts`;调用方仍 `from "…/services/api"` / `"…/types"`。 - 单个组件函数超过 ~500 行视为需要拆分:状态/副作用抽成 `hooks/`,纯计算抽成 `lib/`,区块抽成 `components/`。 (`BizComparePage` `WebcrtPage` `BizStatePage` `BizMigrationPage` `TopologyPage` 等仍是待拆分的单体组件。) ## 样式规范 `src/index.css` 只是入口:按顺序 `@import` `styles/` 下的文件,**导入顺序即层叠顺序,不要调换**。 ``` styles/ theme/tokens.css # 唯一的色板/圆角/阴影来源(--nx-*),并把旧 --nm-* 映射到它 base/ # 全局基础(滚动条、body) layout/ # 应用壳、网络/审计壳(左侧导航) components/ # 共享原语(按钮、输入、表格、卡片、pill…) pages/<领域>.css # 页面样式,类名带领域前缀(wb-* topo-* webcrt-* bs-* pt-*…) legacy/ # 历史遗留的覆盖层,逐步并入 components/,不要往里加新规则 theme/theme.css # 设计语言层,必须最后加载 ``` 设计方向:**现代蓝**——沿用 NetX 原有深海军蓝色板、柔和渐变面板、8/12px 圆角、蓝色强调色(主按钮用渐变), 胶囊状态标签;hover 有轻微抬升和蓝色光晕,focus 用柔和焦点环。 规则: - 颜色一律用 `var(--nx-*)`;半透明用 `rgb(var(--nx-ink-rgb) / .6)`,不要写 `#0f172a` / `rgba(15,23,42,.6)`。 - 状态色用语义变量 `--nx-ok / --nx-warn / --nx-bad`。 - 次要文字至少用 `--nx-text-faint`(在 `--nx-surface-1` 上对比度 ≥ 4.5:1)。 - 顶栏高度固定 48px(`--nx-header-h`),多个页面用 `calc(100vh - 48px)` 布局。 - 新样式放进所属 `pages/` 或 `components/` 文件;只有跨页面的视觉语言才改 `theme/theme.css`。 - 避免 `!important` 和"覆盖全局 button"式规则;需要时先在 `components/` 里改原语。 ## 路由与模块 | 路径 | 页面 | moduleId | |------|------|----------| | `/` | 工作台 | — | | `/ume` | UME 同步 | `ume` | | `/ne` | 网元管理(CRUD / 连通性) | `ne` | | `/network` | 网络管理(左栏 + 子路由) | `network` | | `/network/devices` | 设备列表(只读:托管 + UME) | `network` | | `/network/alarms` | 告警信息 | `network` | | `/network/configs` | 配置信息(同步快照) | `network` | | `/network/webcrt` | redirect → `/webcrt`(保留 query) | — | | `/network/topology` | redirect → `/topology` | — | | `/network/tasks/collect` | 采集任务 | `network` | | `/network/tasks/config-sync` | 配置同步 | `network` | | `/network/tasks/port-traffic` | 端口流量监控(设备管理) | `network` | | `/network/tasks/port-traffic/wall` | 流量大屏列表(打开独立页签) | `network` | | `/network/tasks/biz-state` | 业务状态监控(LLDP / VRF 关联采集) | `network` | | `/network/tasks/biz-compare` | 业务状态比对(模板 + 任务 + pre/post 结果) | `network` | | `/port-traffic/wall/:boardId` | 流量大屏专有页签(无网络侧栏) | `port-traffic-wall` | | `/topology` | 拓扑管理(模式化编辑器:选择/平移/拖动/连线、框选、自动布局、拖放添加) | `topology` | | `/webcrt` | WebCRT 终端 | `webcrt` | | `/collect` | redirect → `/network/tasks/collect` | — | 菜单树见 `config/networkNav.ts`。**新增网络子页:** 1. 在 `networkNav.ts` 增加叶子项 2. 在 `App.tsx` `/network` 下增加子 `` 3. 补充 i18n `network.*` **新增工作台模块仍改 `config/modules.ts`:** 1. 在 `MODULES` 增加一项(`moduleId`、`path`、`section`、i18n key、`iconTone`) 2. 在 `App.tsx` 增加 `` 3. 在 `i18n/locales/zh/.ts` 与 `en/.ts` 补充文案(新命名空间还需在各自 `index.ts` 注册) 4. 工作台卡片由 `modulesInSection` 自动生成,点击走 `openOrFocusModule` ## 跨标签页导航 - **工作台 → 模块**:`openOrFocusModule`(同 `moduleId` 仅一个标签,已打开则聚焦) - **模块 → 工作台**:顶栏四格图标,`returnToWorkbench`(聚焦原工作台标签;已关闭则新开) - **流量大屏**:列表在网络管理内;打开单板用 `openNewModuleWindow`(`/port-traffic/wall/:boardId`),与 `network` 单例页签隔离 - 实现:`utils/tabChannel.ts` + 命名窗口 `netx-module-{id}` / `netx-workbench` ## i18n - 所有面向用户的文案走 `useI18n().t("dotted.key")` - 表头/字段名等技术列可保持英文 - 语言存在 `localStorage`(`netx-locale`),顶栏 ⋯ 切换 ## 数据请求 - API 仅写在 `services/api/<领域>.ts` - Query key 集中在 `constants/queryKeys.ts` - 失效缓存用 prefix key(如 `queryKeys.umeSyncStatusAll`) - 顶栏连接状态:`App` 轮询 `GET /v1/integrations/status`(5s),展示 **netx api** 与 **DSH alarm hub** - 关键告警推送:NetX **DSH alarm hub**(`/v1/integrations/dsh-alarm/ws`)为服务器,Netx Ops 外拨订阅;UME 页展示多订阅连接列表 ## 网元管理(独立于 UME) - API:`/v1/managed-ne/*`(CRUD、导入、连通性测试) - 环境变量:`NETX_CREDENTIAL_SECRET_KEY`(Fernet,用于加密存储 SSH 密码) - 导入列:`device_type,ip,username,password,port,protocol,name,vendor` - 连通性测试成功后会**始终**用探测到的设备名覆盖「名称」 ## 批量采集 - API:`/v1/ne-collections/*`(仅 `connect_status=pass` 的网元可参与) - 采集日志目录:`NETX_NE_COLLECTION_DATA_DIR`(默认 `data/ne_collections`) - 命令每行一条,`#` 为注释;输出格式与旧版 NetX 采集 `.txt` 一致 ## 配置同步 - API:`/v1/config-sync/*`(策略、看板、周期、快照) - 存储:PostgreSQL `ne_config_snapshot` / `ne_config_history`(zlib BYTEA) - 范围:ManagedNE + UME CLI 目标;厂商固定只读命令矩阵 - 调度:`NETX_CONFIG_SYNC_SCHEDULER_ENABLED`(默认开),周期天数策略可配(默认 3 天) - 默认策略:`enabled=false`(首次无自动任务,需在页面手动开启周期调度或点「立即同步」) - 单飞:同一时刻只允许一个 `running|pending|paused` 周期;上轮未结束时不会开启新周期 - 周期状态:全部任务跑完即为 `success`;单网元失败只计入 `fail_count`,不把整轮标为失败 - 崩溃续跑:启动时把中断的 `running` 任务重新入队并继续,占用单飞槽位,避免与新周期重叠 - 进程启动宽限:`NETX_CONFIG_SYNC_STARTUP_GRACE_SEC`(默认 3600)仅约束**新建**自动周期,不影响续跑 - 前端:`/network/tasks/config-sync`(看板)+ `/network/configs`(查看) ## 端口流量监控 - API:`/v1/port-traffic/devices/*`(设备 CRUD、interfaces、samples、compare、dashboard);`discover/ports` 仍按网元拉取接口 - **设备中心**:每台网元(`source`+`ne_id`)一份监控配置;周期/保留/启停挂在设备上;接口归属于该设备 - 唯一性:同一物理口 `(source, ne_id, ifname)` 全局仅允许一个 **active** 监控行,避免重复 CLI 采集 - 调度:按设备到期;一轮采集对该设备 **登录一次** 后批量 `show interface` - 手工映射:大屏可选其它设备接口作基线叠图;可与周期偏移叠加 - 厂商:ZTE / 华为 / 思科;解析速率 **bit/s**;缺厂商 util 时按 bps/bw 回算 - 调度开关:`NETX_PORT_TRAFFIC_SCHEDULER_ENABLED`(默认开),tick `NETX_PORT_TRAFFIC_SCHEDULER_TICK_SEC`(默认 15) - 保留:按设备 `retention_days` 清理过期 sample(周对比建议 ≥8 天) - 前端:`/network/tasks/port-traffic`(设备列表 / 向导 / 编辑 / 采集日志);`/network/tasks/port-traffic/wall`(大屏列表);`/port-traffic/wall/:boardId`(独立大屏页签,`openNewModuleWindow`,与网络管理页签互不影响) - 定制大屏:`/v1/port-traffic/boards*`(Board + Panel);整板 PUT panels 保存;刷新/换页不丢;图数据仍走 compare - 采集日志:`GET /v1/port-traffic/devices/{id}/events`;失败写入 `port_traffic_event`,列表操作可查看 - 支持拓扑深链:`?ne_id=&source=managed|ume&ifname=` 打开向导并预填网元 ## 业务状态监控(biz_state) - API:`/v1/biz-state/profiles`、`/discover`、`/tasks*`、`/batches*`、`/batches/{id}/export`、`/compare/*` - **ParseProfile**:命令模板 + TextFSM + 回调 + schema;LLDP 与拓扑 **共享解析**(`lldp_shared`),业务流程写批次表,拓扑写 Fabric - Phase1:LLDP 邻居快照;建任务默认启用对应厂商 LLDP profile;支持自定义只采不解析行 - Phase2:比对模板(多监控表 / Key·接口·比对字段;空比对=仅条目)、端口映射、CompareJob 列表与详情、先选批次再按 Excel sheet 看 pre/post、`/network/tasks/biz-compare` - API 补充:`GET /v1/biz-state/compare/metrics`;模板 `metrics[]` 多表结构 - Phase3:VRF 发现(`*.vrf_list`)供带参采集绑定;`PUT …/items/{id}/bindings` - 调度:`NETX_BIZ_STATE_SCHEDULER_ENABLED`(默认开),tick `NETX_BIZ_STATE_SCHEDULER_TICK_SEC` - **双车道采集**:监控项 `collect_lane=light|heavy`(UI「采集车道」列)。轻/重各一条 SSH;整轮墙钟上限 = `RUN_TIMEOUT_CAP`(命令多时易顶满)。 - 轻车道:`NETX_NE_COLLECT_READ_TIMEOUT_SEC`(单条,默认 120)· `NETX_NE_COLLECT_RUN_TIMEOUT_CAP_SEC`(整轮,默认 **600≈10min**) - 重车道:`NETX_BIZ_STATE_HEAVY_READ_TIMEOUT_SEC`(单条,默认 1500)· `NETX_BIZ_STATE_HEAVY_RUN_TIMEOUT_CAP_SEC`(整轮,默认 **2400≈40min**) - 批次约 40 分钟以 `*_timeout (2400s)` / `partial` 结束时,优先加大 `NETX_BIZ_STATE_HEAVY_RUN_TIMEOUT_CAP_SEC`(见 `.env.example`) - 停止采集:`POST /v1/biz-state/tasks/{id}/collect/stop`(排队直接取消;执行中命令间隙中止) - 前端:`/network/tasks/biz-state`(勾选监控项 / 发现 VRF / 批次 / 导出 zip) ## 拓扑管理(Fabric + 站点目录,对齐厂商) - 三层库存:**运维** `managed_ne`(凭据/采集/WebCRT)· **EMS** `ume_inventory_ne` · **拓扑投影** `topo_fabric_node`(上图/LLDP/分类)。Fabric 由 ensure 按需创建,与运维表不是同一张表。 - 删除运维网元(手工 / WebCRT / `ume_sync` ManagedNE 同一路径):先解绑 `fabric.managed_ne_id`(及 view membership)。若该 Fabric 节点同时仍绑 UME → 变为 UME-only,**保留**节点与直连边;若解绑后已无 managed 且无 UME(完全孤儿)→ **硬删** Fabric 节点并级联直连边 / placements。UME 库存 reconcile 对称解绑 `ume_ne_id`,同样在完全孤儿时 purge。历史悬空引用与孤儿 GC:`POST /fabric/reconcile-links`(`cleanup-duplicates` 先 merge/absorb 再扫孤儿);后台默认每 6h 跑一次(`fabric_reconcile_scheduler_*`,可关)。 - 画布:移出本图仅影响 placement(保存落库);连线/删边本地排队、**保存**才写/删 Fabric(Undo 仅保存前有效);topology/lldp 占位可右键硬删(ManagedNE+Fabric+边)。未匹配且未建占位的对端不上 Fabric 边。 - 事实库:`topo_fabric_node` / `topo_fabric_edge`(按 5 万网元 / 100 万链路设计;物理层仅 LLDP) - 站点树:`topo_folder`(系统隐藏 `root`;用户新建站点/区域,无默认「未分区」) - 拓扑图:`topo_view.kind=physical|custom`(同站点下平级;建区域后需手工/MCP 建画布)+ `topo_view_node` - 边界:图 `filter.membership`(max_nodes / expand_hops / frozen);`project-neighbors` / `populate` 不得无界灌全网。画布「投影邻居 / 发现后投影」默认 `dry_run`:只进本地脏图,**保存**时 `addTopologyViewNodes` 才落库。 - 连线模式:网元中心锚点拖拽连线;默认直线 `straight`(与发现边同一附着点)。 - API:`/v1/topology/tree`、`/folders*`、`/fabric/*`、`/views*`(含 `populate`、`kind`) - 前端:左侧站点→物理/自定义图;右侧目录浏览,打开本图进设备画布;「添加网元」支持勾选批量上图(也可拖放单台) - MCP:以 `queryTopologyEdges` 为主查询 Fabric;画布编辑走 Web - BGP / 隧道 / L2VPN:`layer` 预留,实现 TODO ## 分类(清单打标) - Fabric 主键:`topo_fabric_node.level`(major.minor,越小越靠外);`role` 为 `floor(level)` 同步别名 - 预设:0 外部 / 1 核心 / 2 汇聚 / 3 接入;子层如 1.1、2.1 - 主流程:清单 + 正则 → 批量写 level/区域;支持单行编辑 - API:`GET /fabric/nodes`(level / level_major / role / unmatched=level)、`POST …/match`、`POST …/tags/bulk`(`level`)、`PATCH …/tags` - Agent:`classifyTopologyFabricNodes`(netx-topology MCP) - 前端:网络管理 →「分类」 ## WebCRT - API:`POST /v1/webcrt/sessions`(`ne_id` 或 `ume_ne_id`;可选 `encoding`/`post_login_commands`/`async_connect`)、`WS /v1/webcrt/sessions/{id}/ws`、`DELETE /v1/webcrt/sessions/{id}` - SFTP(直连 SSH):`POST /v1/webcrt/sftp/list|download|upload` - 目标列表复用 `/v1/cli/targets`(托管 + UME,搜索分页;`source=all|managed|ume`) - 凭据:托管走网元自身账号;UME 走 CLI 连接模板(`resolve_cli_target`) - 前端:CRT 风格左右分栏(收藏/最近 + 多标签终端);粘贴节流、选区复制、Break、Ctrl+F、Button Bar、广播、编码/字号 - WS:stdout 二进制帧合批;stdin 短合批;建连可异步(POST 立即返回,进度经 WS `connecting`→`connected`) - 审计 / 会话日志:`NETX_WEBCRT_DATA_DIR`(`audit.jsonl` + `sessions/*.log`) - 限流 / 保活:`NETX_WEBCRT_MAX_SESSIONS`、`NETX_WEBCRT_IDLE_TIMEOUT_SEC`、`NETX_WEBCRT_KEEPALIVE_SEC`、`NETX_WEBCRT_ANTI_IDLE_SEC` ## Toast - 使用 `ToastProvider`(`main.tsx`)+ `useToast()` - 禁止页面内单独维护一套 toast 状态 ## 样式 - 全局样式:见「样式规范」(`index.css` 为入口,令牌在 `styles/theme/tokens.css`) - 不使用 CSS Modules;类名 BEM 风格:`app-brand__logo`、`wb-card` ## 构建 ```bash cd web && npm run build ``` 开发:`npm run dev`(需后端 API 或 Vite proxy 配置)