mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Restore NE management to the workbench and simplify network device list.
Network Management devices page is a read-only managed+UME inventory; full CRUD stays on /ne. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
87018721a3
commit
a95c6598bb
7 changed files with 158 additions and 9 deletions
|
|
@ -33,8 +33,9 @@ src/
|
|||
|------|------|----------|
|
||||
| `/` | 工作台 | — |
|
||||
| `/ume` | UME 同步 | `ume` |
|
||||
| `/ne` | 网元管理(CRUD / 连通性) | `ne` |
|
||||
| `/network` | 网络管理(左栏 + 子路由) | `network` |
|
||||
| `/network/devices` | 设备列表 | `network` |
|
||||
| `/network/devices` | 设备列表(只读:托管 + UME) | `network` |
|
||||
| `/network/alarms` | 告警信息 | `network` |
|
||||
| `/network/configs` | 配置信息(同步快照) | `network` |
|
||||
| `/network/webcrt` | redirect → `/webcrt`(保留 query) | — |
|
||||
|
|
@ -44,7 +45,6 @@ src/
|
|||
| `/network/tasks/port-traffic` | 端口流量(占位) | `network` |
|
||||
| `/topology` | 拓扑管理 | `topology` |
|
||||
| `/webcrt` | WebCRT 终端 | `webcrt` |
|
||||
| `/ne` | redirect → `/network/devices` | — |
|
||||
| `/collect` | redirect → `/network/tasks/collect` | — |
|
||||
|
||||
菜单树见 `config/networkNav.ts`。**新增网络子页:**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { UmePage } from "./pages/UmePage";
|
|||
import { WebcrtPage } from "./pages/WebcrtPage";
|
||||
import { TopologyPage } from "./pages/TopologyPage";
|
||||
import { NetworkLayout } from "./pages/network/NetworkLayout";
|
||||
import { NetworkDevicesPage } from "./pages/network/NetworkDevicesPage";
|
||||
import { NetworkAlarmsPage } from "./pages/network/NetworkAlarmsPage";
|
||||
import { NetworkConfigsPage } from "./pages/network/NetworkConfigsPage";
|
||||
import { NetworkPlaceholderPage } from "./pages/network/NetworkPlaceholderPage";
|
||||
|
|
@ -82,11 +83,12 @@ function ProtectedApp() {
|
|||
<Route path="/" element={<WorkbenchPage />} />
|
||||
<Route path="/workbench" element={<Navigate to="/" replace />} />
|
||||
<Route path="/ume" element={<UmePage />} />
|
||||
<Route path="/ne" element={<NePage />} />
|
||||
<Route path="/topology" element={<TopologyPage />} />
|
||||
<Route path="/webcrt" element={<WebcrtPage />} />
|
||||
<Route path="/network" element={<NetworkLayout />}>
|
||||
<Route index element={<Navigate to="devices" replace />} />
|
||||
<Route path="devices" element={<NePage />} />
|
||||
<Route path="devices" element={<NetworkDevicesPage />} />
|
||||
<Route path="alarms" element={<NetworkAlarmsPage />} />
|
||||
<Route path="configs" element={<NetworkConfigsPage />} />
|
||||
<Route path="topology" element={<Navigate to="/topology" replace />} />
|
||||
|
|
@ -95,7 +97,6 @@ function ProtectedApp() {
|
|||
<Route path="tasks/config-sync" element={<ConfigSyncPage />} />
|
||||
<Route path="tasks/port-traffic" element={<NetworkPlaceholderPage kind="port-traffic" />} />
|
||||
</Route>
|
||||
<Route path="/ne" element={<Navigate to="/network/devices" replace />} />
|
||||
<Route path="/collect" element={<Navigate to="/network/tasks/collect" replace />} />
|
||||
<Route path="/users" element={<UsersPage />} />
|
||||
<Route path="/audit" element={<AuditPage />} />
|
||||
|
|
|
|||
|
|
@ -26,13 +26,22 @@ export const MODULES: readonly ModuleDefinition[] = [
|
|||
iconTone: "blue",
|
||||
titleKey: "layout.titleUme",
|
||||
},
|
||||
{
|
||||
moduleId: "ne",
|
||||
path: "/ne",
|
||||
section: "operations",
|
||||
labelKey: "workbench.cards.managedNe",
|
||||
descKey: "workbench.cards.managedNeDesc",
|
||||
iconTone: "green",
|
||||
titleKey: "layout.titleManagedNe",
|
||||
},
|
||||
{
|
||||
moduleId: "network",
|
||||
path: "/network",
|
||||
section: "operations",
|
||||
labelKey: "workbench.cards.network",
|
||||
descKey: "workbench.cards.networkDesc",
|
||||
iconTone: "green",
|
||||
iconTone: "slate",
|
||||
titleKey: "layout.titleNetwork",
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const en = {
|
|||
umeSync: "UME Sync",
|
||||
umeSyncDesc: "UME alarms, subscription & inventory",
|
||||
network: "Network Management",
|
||||
networkDesc: "Devices, alarms, config sync, and monitoring tasks",
|
||||
networkDesc: "NE inventory, alarms, config sync, and monitoring tasks",
|
||||
managedNe: "NE Management",
|
||||
managedNeDesc: "Multi-vendor SSH/Telnet NEs & connectivity test",
|
||||
collect: "Batch Collect",
|
||||
|
|
@ -115,6 +115,19 @@ const en = {
|
|||
message: "Message",
|
||||
},
|
||||
},
|
||||
networkDevices: {
|
||||
title: "Device list",
|
||||
keywordPh: "Name / IP / ID",
|
||||
allSource: "All sources",
|
||||
empty: "No network elements.",
|
||||
col: {
|
||||
source: "Source",
|
||||
name: "Name",
|
||||
vendor: "Vendor",
|
||||
deviceType: "Device type",
|
||||
connect: "Connect",
|
||||
},
|
||||
},
|
||||
networkConfigs: {
|
||||
title: "Configurations",
|
||||
keywordPh: "Name / IP / ID",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const zh = {
|
|||
umeSync: "UME同步",
|
||||
umeSyncDesc: "UME 告警同步、订阅与清单",
|
||||
network: "网络管理",
|
||||
networkDesc: "设备、告警、配置同步与监控任务",
|
||||
networkDesc: "网元清单、告警、配置同步与监控任务",
|
||||
managedNe: "网元管理",
|
||||
managedNeDesc: "多厂商 SSH/Telnet 网元与连通性测试",
|
||||
collect: "批量采集",
|
||||
|
|
@ -115,6 +115,19 @@ const zh = {
|
|||
message: "消息",
|
||||
},
|
||||
},
|
||||
networkDevices: {
|
||||
title: "设备列表",
|
||||
keywordPh: "名称 / IP / ID",
|
||||
allSource: "全部来源",
|
||||
empty: "暂无网元。",
|
||||
col: {
|
||||
source: "来源",
|
||||
name: "名称",
|
||||
vendor: "厂商",
|
||||
deviceType: "设备类型",
|
||||
connect: "连通状态",
|
||||
},
|
||||
},
|
||||
networkConfigs: {
|
||||
title: "配置信息",
|
||||
keywordPh: "名称 / IP / ID",
|
||||
|
|
|
|||
|
|
@ -697,8 +697,8 @@ export function TopologyPage() {
|
|||
const umeId = node?.data.ume_ne_id;
|
||||
if (managedId) {
|
||||
openOrFocusModule({
|
||||
moduleId: "network",
|
||||
path: "/network/devices",
|
||||
moduleId: "ne",
|
||||
path: "/ne",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
113
web/src/pages/network/NetworkDevicesPage.tsx
Normal file
113
web/src/pages/network/NetworkDevicesPage.tsx
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchCliTargets } from "../../services/api";
|
||||
import { queryKeys } from "../../constants/queryKeys";
|
||||
import { useI18n } from "../../i18n";
|
||||
import { pageCount } from "../../utils/display";
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
/** Read-only inventory of managed + UME NEs for Network Management. */
|
||||
export function NetworkDevicesPage() {
|
||||
const { t } = useI18n();
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [source, setSource] = useState<"all" | "managed" | "ume">("all");
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: queryKeys.cliTargets(`${source}:${keyword}`, page, PAGE_SIZE),
|
||||
queryFn: () => fetchCliTargets({ source, keyword, page, pageSize: PAGE_SIZE }),
|
||||
staleTime: 5000,
|
||||
});
|
||||
|
||||
const items = listQuery.data?.items ?? [];
|
||||
const total = Number(listQuery.data?.total || 0);
|
||||
const pages = pageCount(total, PAGE_SIZE);
|
||||
|
||||
return (
|
||||
<section className="panel">
|
||||
<div className="panel__toolbar">
|
||||
<h2>{t("networkDevices.title")}</h2>
|
||||
</div>
|
||||
|
||||
<div className="filter-inline">
|
||||
<input
|
||||
value={keyword}
|
||||
placeholder={t("networkDevices.keywordPh")}
|
||||
onChange={(e) => {
|
||||
setKeyword(e.target.value);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
<select
|
||||
value={source}
|
||||
onChange={(e) => {
|
||||
setSource(e.target.value as "all" | "managed" | "ume");
|
||||
setPage(1);
|
||||
}}
|
||||
>
|
||||
<option value="all">{t("networkDevices.allSource")}</option>
|
||||
<option value="managed">managed</option>
|
||||
<option value="ume">ume</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!keyword.trim() && source === "all"}
|
||||
onClick={() => {
|
||||
setKeyword("");
|
||||
setSource("all");
|
||||
setPage(1);
|
||||
}}
|
||||
>
|
||||
{t("common.clearFilters")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{listQuery.isLoading ? <p className="muted">{t("common.refreshing")}</p> : null}
|
||||
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("networkDevices.col.source")}</th>
|
||||
<th>{t("networkDevices.col.name")}</th>
|
||||
<th>IP</th>
|
||||
<th>{t("networkDevices.col.vendor")}</th>
|
||||
<th>{t("networkDevices.col.deviceType")}</th>
|
||||
<th>{t("networkDevices.col.connect")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((row) => (
|
||||
<tr key={`${row.source}:${row.id}`}>
|
||||
<td>{row.source}</td>
|
||||
<td>{row.name || row.id}</td>
|
||||
<td>{row.ip_address || "-"}</td>
|
||||
<td>{row.vendor || "-"}</td>
|
||||
<td>{row.device_type || row.ne_type || "-"}</td>
|
||||
<td>{row.connect_status || "-"}</td>
|
||||
</tr>
|
||||
))}
|
||||
{!items.length && !listQuery.isLoading ? (
|
||||
<tr>
|
||||
<td colSpan={6} className="muted">
|
||||
{t("networkDevices.empty")}
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="pager">
|
||||
<button type="button" disabled={page <= 1} onClick={() => setPage((p) => p - 1)}>
|
||||
{t("common.prevPage")}
|
||||
</button>
|
||||
<span className="muted">
|
||||
{t("common.pagerMeta", { total: String(total), page: String(page), pages: String(pages) })}
|
||||
</span>
|
||||
<button type="button" disabled={page >= pages} onClick={() => setPage((p) => p + 1)}>
|
||||
{t("common.nextPage")}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue