mirror of
https://github.com/hansjone/netx.git
synced 2026-10-12 05:20:44 +08:00
Restore topology and WebCRT as workbench modules.
Also tighten config-sync policy form alignment and make the network nav collapse control icon-only. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
f5b4a14c0f
commit
abb5172a8f
11 changed files with 97 additions and 37 deletions
|
|
@ -35,17 +35,17 @@ src/
|
||||||
| `/ume` | UME 同步 | `ume` |
|
| `/ume` | UME 同步 | `ume` |
|
||||||
| `/network` | 网络管理(左栏 + 子路由) | `network` |
|
| `/network` | 网络管理(左栏 + 子路由) | `network` |
|
||||||
| `/network/devices` | 设备列表 | `network` |
|
| `/network/devices` | 设备列表 | `network` |
|
||||||
| `/network/topology` | 拓扑信息 | `network` |
|
|
||||||
| `/network/alarms` | 告警信息 | `network` |
|
| `/network/alarms` | 告警信息 | `network` |
|
||||||
| `/network/configs` | 配置信息(同步快照) | `network` |
|
| `/network/configs` | 配置信息(同步快照) | `network` |
|
||||||
| `/network/webcrt` | WebCRT 终端 | `network` |
|
| `/network/webcrt` | redirect → `/webcrt`(保留 query) | — |
|
||||||
|
| `/network/topology` | redirect → `/topology` | — |
|
||||||
| `/network/tasks/collect` | 采集任务 | `network` |
|
| `/network/tasks/collect` | 采集任务 | `network` |
|
||||||
| `/network/tasks/config-sync` | 配置同步 | `network` |
|
| `/network/tasks/config-sync` | 配置同步 | `network` |
|
||||||
| `/network/tasks/port-traffic` | 端口流量(占位) | `network` |
|
| `/network/tasks/port-traffic` | 端口流量(占位) | `network` |
|
||||||
|
| `/topology` | 拓扑管理 | `topology` |
|
||||||
|
| `/webcrt` | WebCRT 终端 | `webcrt` |
|
||||||
| `/ne` | redirect → `/network/devices` | — |
|
| `/ne` | redirect → `/network/devices` | — |
|
||||||
| `/collect` | redirect → `/network/tasks/collect` | — |
|
| `/collect` | redirect → `/network/tasks/collect` | — |
|
||||||
| `/topology` | redirect → `/network/topology` | — |
|
|
||||||
| `/webcrt` | redirect → `/network/webcrt`(保留 query) | — |
|
|
||||||
|
|
||||||
菜单树见 `config/networkNav.ts`。**新增网络子页:**
|
菜单树见 `config/networkNav.ts`。**新增网络子页:**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ import { ForceChangePasswordPage } from "./pages/ForceChangePasswordPage";
|
||||||
import { fetchIntegrationStatus } from "./services/api";
|
import { fetchIntegrationStatus } from "./services/api";
|
||||||
import { useAuth } from "./auth/AuthContext";
|
import { useAuth } from "./auth/AuthContext";
|
||||||
|
|
||||||
/** Preserve ?ne_id=… when redirecting legacy /webcrt into the network module. */
|
/** Preserve query when redirecting legacy /network/webcrt → /webcrt. */
|
||||||
function WebcrtLegacyRedirect() {
|
function NetworkWebcrtRedirect() {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
return <Navigate to={`/network/webcrt${search}`} replace />;
|
return <Navigate to={`/webcrt${search}`} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProtectedApp() {
|
function ProtectedApp() {
|
||||||
|
|
@ -82,21 +82,21 @@ function ProtectedApp() {
|
||||||
<Route path="/" element={<WorkbenchPage />} />
|
<Route path="/" element={<WorkbenchPage />} />
|
||||||
<Route path="/workbench" element={<Navigate to="/" replace />} />
|
<Route path="/workbench" element={<Navigate to="/" replace />} />
|
||||||
<Route path="/ume" element={<UmePage />} />
|
<Route path="/ume" element={<UmePage />} />
|
||||||
|
<Route path="/topology" element={<TopologyPage />} />
|
||||||
|
<Route path="/webcrt" element={<WebcrtPage />} />
|
||||||
<Route path="/network" element={<NetworkLayout />}>
|
<Route path="/network" element={<NetworkLayout />}>
|
||||||
<Route index element={<Navigate to="devices" replace />} />
|
<Route index element={<Navigate to="devices" replace />} />
|
||||||
<Route path="devices" element={<NePage />} />
|
<Route path="devices" element={<NePage />} />
|
||||||
<Route path="topology" element={<TopologyPage />} />
|
|
||||||
<Route path="alarms" element={<NetworkAlarmsPage />} />
|
<Route path="alarms" element={<NetworkAlarmsPage />} />
|
||||||
<Route path="configs" element={<NetworkConfigsPage />} />
|
<Route path="configs" element={<NetworkConfigsPage />} />
|
||||||
<Route path="webcrt" element={<WebcrtPage />} />
|
<Route path="topology" element={<Navigate to="/topology" replace />} />
|
||||||
|
<Route path="webcrt" element={<NetworkWebcrtRedirect />} />
|
||||||
<Route path="tasks/collect" element={<CollectPage />} />
|
<Route path="tasks/collect" element={<CollectPage />} />
|
||||||
<Route path="tasks/config-sync" element={<ConfigSyncPage />} />
|
<Route path="tasks/config-sync" element={<ConfigSyncPage />} />
|
||||||
<Route path="tasks/port-traffic" element={<NetworkPlaceholderPage kind="port-traffic" />} />
|
<Route path="tasks/port-traffic" element={<NetworkPlaceholderPage kind="port-traffic" />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/ne" element={<Navigate to="/network/devices" replace />} />
|
<Route path="/ne" element={<Navigate to="/network/devices" replace />} />
|
||||||
<Route path="/collect" element={<Navigate to="/network/tasks/collect" replace />} />
|
<Route path="/collect" element={<Navigate to="/network/tasks/collect" replace />} />
|
||||||
<Route path="/topology" element={<Navigate to="/network/topology" replace />} />
|
|
||||||
<Route path="/webcrt" element={<WebcrtLegacyRedirect />} />
|
|
||||||
<Route path="/users" element={<UsersPage />} />
|
<Route path="/users" element={<UsersPage />} />
|
||||||
<Route path="/audit" element={<AuditPage />} />
|
<Route path="/audit" element={<AuditPage />} />
|
||||||
<Route path="/api-keys" element={<ApiTokensPage />} />
|
<Route path="/api-keys" element={<ApiTokensPage />} />
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,24 @@ export const MODULES: readonly ModuleDefinition[] = [
|
||||||
iconTone: "green",
|
iconTone: "green",
|
||||||
titleKey: "layout.titleNetwork",
|
titleKey: "layout.titleNetwork",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
moduleId: "topology",
|
||||||
|
path: "/topology",
|
||||||
|
section: "operations",
|
||||||
|
labelKey: "workbench.cards.topology",
|
||||||
|
descKey: "workbench.cards.topologyDesc",
|
||||||
|
iconTone: "amber",
|
||||||
|
titleKey: "layout.titleTopology",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
moduleId: "webcrt",
|
||||||
|
path: "/webcrt",
|
||||||
|
section: "operations",
|
||||||
|
labelKey: "workbench.cards.webcrt",
|
||||||
|
descKey: "workbench.cards.webcrtDesc",
|
||||||
|
iconTone: "slate",
|
||||||
|
titleKey: "layout.titleWebcrt",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
moduleId: "users",
|
moduleId: "users",
|
||||||
path: "/users",
|
path: "/users",
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,8 @@ export const NETWORK_NAV: readonly NetworkNavGroup[] = [
|
||||||
labelKey: "network.nav.detail",
|
labelKey: "network.nav.detail",
|
||||||
items: [
|
items: [
|
||||||
{ id: "devices", path: "/network/devices", labelKey: "network.nav.devices", group: "detail" },
|
{ id: "devices", path: "/network/devices", labelKey: "network.nav.devices", group: "detail" },
|
||||||
{ id: "topology", path: "/network/topology", labelKey: "network.nav.topology", group: "detail" },
|
|
||||||
{ id: "alarms", path: "/network/alarms", labelKey: "network.nav.alarms", group: "detail" },
|
{ id: "alarms", path: "/network/alarms", labelKey: "network.nav.alarms", group: "detail" },
|
||||||
{ id: "configs", path: "/network/configs", labelKey: "network.nav.configs", group: "detail" },
|
{ id: "configs", path: "/network/configs", labelKey: "network.nav.configs", group: "detail" },
|
||||||
{ id: "webcrt", path: "/network/webcrt", labelKey: "network.nav.webcrt", group: "detail" },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const en = {
|
||||||
umeSync: "UME Sync",
|
umeSync: "UME Sync",
|
||||||
umeSyncDesc: "UME alarms, subscription & inventory",
|
umeSyncDesc: "UME alarms, subscription & inventory",
|
||||||
network: "Network Management",
|
network: "Network Management",
|
||||||
networkDesc: "Devices, topology, alarms, WebCRT, and monitoring tasks",
|
networkDesc: "Devices, alarms, config sync, and monitoring tasks",
|
||||||
managedNe: "NE Management",
|
managedNe: "NE Management",
|
||||||
managedNeDesc: "Multi-vendor SSH/Telnet NEs & connectivity test",
|
managedNeDesc: "Multi-vendor SSH/Telnet NEs & connectivity test",
|
||||||
collect: "Batch Collect",
|
collect: "Batch Collect",
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const zh = {
|
||||||
umeSync: "UME同步",
|
umeSync: "UME同步",
|
||||||
umeSyncDesc: "UME 告警同步、订阅与清单",
|
umeSyncDesc: "UME 告警同步、订阅与清单",
|
||||||
network: "网络管理",
|
network: "网络管理",
|
||||||
networkDesc: "设备、拓扑、告警、WebCRT 与监控任务",
|
networkDesc: "设备、告警、配置同步与监控任务",
|
||||||
managedNe: "网元管理",
|
managedNe: "网元管理",
|
||||||
managedNeDesc: "多厂商 SSH/Telnet 网元与连通性测试",
|
managedNeDesc: "多厂商 SSH/Telnet 网元与连通性测试",
|
||||||
collect: "批量采集",
|
collect: "批量采集",
|
||||||
|
|
|
||||||
|
|
@ -798,6 +798,52 @@ pre {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.config-sync-policy-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-sync-policy-check {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #334155;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-sync-policy-check input {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-sync-policy-field {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #334155;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-sync-policy-field input[type="number"] {
|
||||||
|
width: 64px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-sync-policy-field select {
|
||||||
|
width: auto;
|
||||||
|
min-width: 110px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ne-stats-card {
|
.ne-stats-card {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
}
|
}
|
||||||
|
|
@ -2708,12 +2754,13 @@ pre {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 1px solid #cbd5e1;
|
border: 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
color: #475569;
|
color: #64748b;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main .network-nav__collapse-btn:hover,
|
.app-main .network-nav__collapse-btn:hover,
|
||||||
|
|
|
||||||
|
|
@ -235,46 +235,43 @@ export function ConfigSyncPage() {
|
||||||
|
|
||||||
<div className="panel" style={{ marginBottom: 16 }}>
|
<div className="panel" style={{ marginBottom: 16 }}>
|
||||||
<h3>{t("configSync.policyTitle")}</h3>
|
<h3>{t("configSync.policyTitle")}</h3>
|
||||||
<div className="filter-inline" style={{ flexWrap: "wrap", gap: 12 }}>
|
<div className="config-sync-policy-row">
|
||||||
<label>
|
<label className="config-sync-policy-check">
|
||||||
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />{" "}
|
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />
|
||||||
{t("configSync.enabled")}
|
<span>{t("configSync.enabled")}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label className="config-sync-policy-field">
|
||||||
{t("configSync.intervalDays")}{" "}
|
<span>{t("configSync.intervalDays")}</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={365}
|
max={365}
|
||||||
value={intervalDays}
|
value={intervalDays}
|
||||||
onChange={(e) => setIntervalDays(Math.max(1, Number(e.target.value) || 1))}
|
onChange={(e) => setIntervalDays(Math.max(1, Number(e.target.value) || 1))}
|
||||||
style={{ width: 72 }}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label className="config-sync-policy-field">
|
||||||
{t("configSync.concurrency")}{" "}
|
<span>{t("configSync.concurrency")}</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={30}
|
max={30}
|
||||||
value={concurrency}
|
value={concurrency}
|
||||||
onChange={(e) => setConcurrency(Math.max(1, Math.min(30, Number(e.target.value) || 1)))}
|
onChange={(e) => setConcurrency(Math.max(1, Math.min(30, Number(e.target.value) || 1)))}
|
||||||
style={{ width: 72 }}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label className="config-sync-policy-field">
|
||||||
{t("configSync.historyKeep")}{" "}
|
<span>{t("configSync.historyKeep")}</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
max={30}
|
max={30}
|
||||||
value={historyKeep}
|
value={historyKeep}
|
||||||
onChange={(e) => setHistoryKeep(Math.max(0, Math.min(30, Number(e.target.value) || 0)))}
|
onChange={(e) => setHistoryKeep(Math.max(0, Math.min(30, Number(e.target.value) || 0)))}
|
||||||
style={{ width: 72 }}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label className="config-sync-policy-field">
|
||||||
{t("configSync.scope")}{" "}
|
<span>{t("configSync.scope")}</span>
|
||||||
<select
|
<select
|
||||||
value={scopeMode}
|
value={scopeMode}
|
||||||
onChange={(e) => setScopeMode(e.target.value === "selected" ? "selected" : "all")}
|
onChange={(e) => setScopeMode(e.target.value === "selected" ? "selected" : "all")}
|
||||||
|
|
|
||||||
|
|
@ -781,7 +781,7 @@ export function NePage() {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openOrFocusModule({
|
openOrFocusModule({
|
||||||
moduleId: "network",
|
moduleId: "network",
|
||||||
path: `/network/webcrt?ne_id=${encodeURIComponent(row.id)}`,
|
path: `/webcrt?ne_id=${encodeURIComponent(row.id)}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
title={t("managedNe.openTerminal")}
|
title={t("managedNe.openTerminal")}
|
||||||
|
|
|
||||||
|
|
@ -677,14 +677,14 @@ export function TopologyPage() {
|
||||||
if (managedId) {
|
if (managedId) {
|
||||||
openOrFocusModule({
|
openOrFocusModule({
|
||||||
moduleId: "network",
|
moduleId: "network",
|
||||||
path: `/network/webcrt?ne_id=${encodeURIComponent(managedId)}`,
|
path: `/webcrt?ne_id=${encodeURIComponent(managedId)}`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (umeId) {
|
if (umeId) {
|
||||||
openOrFocusModule({
|
openOrFocusModule({
|
||||||
moduleId: "network",
|
moduleId: "network",
|
||||||
path: `/network/webcrt?ne_id=${encodeURIComponent(umeId)}&source=ume`,
|
path: `/webcrt?ne_id=${encodeURIComponent(umeId)}&source=ume`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export function NetworkConfigsPage() {
|
||||||
{t("networkConfigs.view")}
|
{t("networkConfigs.view")}
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
to={`/network/webcrt?${row.source === "ume" ? "ume_ne_id" : "ne_id"}=${encodeURIComponent(row.target_id)}`}
|
to={`/webcrt?${row.source === "ume" ? "ume_ne_id" : "ne_id"}=${encodeURIComponent(row.target_id)}`}
|
||||||
style={{ marginLeft: 8 }}
|
style={{ marginLeft: 8 }}
|
||||||
>
|
>
|
||||||
WebCRT
|
WebCRT
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue