mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +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` |
|
||||
| `/network` | 网络管理(左栏 + 子路由) | `network` |
|
||||
| `/network/devices` | 设备列表 | `network` |
|
||||
| `/network/topology` | 拓扑信息 | `network` |
|
||||
| `/network/alarms` | 告警信息 | `network` |
|
||||
| `/network/configs` | 配置信息(同步快照) | `network` |
|
||||
| `/network/webcrt` | WebCRT 终端 | `network` |
|
||||
| `/network/webcrt` | redirect → `/webcrt`(保留 query) | — |
|
||||
| `/network/topology` | redirect → `/topology` | — |
|
||||
| `/network/tasks/collect` | 采集任务 | `network` |
|
||||
| `/network/tasks/config-sync` | 配置同步 | `network` |
|
||||
| `/network/tasks/port-traffic` | 端口流量(占位) | `network` |
|
||||
| `/topology` | 拓扑管理 | `topology` |
|
||||
| `/webcrt` | WebCRT 终端 | `webcrt` |
|
||||
| `/ne` | redirect → `/network/devices` | — |
|
||||
| `/collect` | redirect → `/network/tasks/collect` | — |
|
||||
| `/topology` | redirect → `/network/topology` | — |
|
||||
| `/webcrt` | redirect → `/network/webcrt`(保留 query) | — |
|
||||
|
||||
菜单树见 `config/networkNav.ts`。**新增网络子页:**
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ import { ForceChangePasswordPage } from "./pages/ForceChangePasswordPage";
|
|||
import { fetchIntegrationStatus } from "./services/api";
|
||||
import { useAuth } from "./auth/AuthContext";
|
||||
|
||||
/** Preserve ?ne_id=… when redirecting legacy /webcrt into the network module. */
|
||||
function WebcrtLegacyRedirect() {
|
||||
/** Preserve query when redirecting legacy /network/webcrt → /webcrt. */
|
||||
function NetworkWebcrtRedirect() {
|
||||
const { search } = useLocation();
|
||||
return <Navigate to={`/network/webcrt${search}`} replace />;
|
||||
return <Navigate to={`/webcrt${search}`} replace />;
|
||||
}
|
||||
|
||||
function ProtectedApp() {
|
||||
|
|
@ -82,21 +82,21 @@ function ProtectedApp() {
|
|||
<Route path="/" element={<WorkbenchPage />} />
|
||||
<Route path="/workbench" element={<Navigate to="/" replace />} />
|
||||
<Route path="/ume" element={<UmePage />} />
|
||||
<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="topology" element={<TopologyPage />} />
|
||||
<Route path="alarms" element={<NetworkAlarmsPage />} />
|
||||
<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/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="/topology" element={<Navigate to="/network/topology" replace />} />
|
||||
<Route path="/webcrt" element={<WebcrtLegacyRedirect />} />
|
||||
<Route path="/users" element={<UsersPage />} />
|
||||
<Route path="/audit" element={<AuditPage />} />
|
||||
<Route path="/api-keys" element={<ApiTokensPage />} />
|
||||
|
|
|
|||
|
|
@ -35,6 +35,24 @@ export const MODULES: readonly ModuleDefinition[] = [
|
|||
iconTone: "green",
|
||||
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",
|
||||
path: "/users",
|
||||
|
|
|
|||
|
|
@ -24,10 +24,8 @@ export const NETWORK_NAV: readonly NetworkNavGroup[] = [
|
|||
labelKey: "network.nav.detail",
|
||||
items: [
|
||||
{ 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: "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",
|
||||
umeSyncDesc: "UME alarms, subscription & inventory",
|
||||
network: "Network Management",
|
||||
networkDesc: "Devices, topology, alarms, WebCRT, and monitoring tasks",
|
||||
networkDesc: "Devices, alarms, config sync, and monitoring tasks",
|
||||
managedNe: "NE Management",
|
||||
managedNeDesc: "Multi-vendor SSH/Telnet NEs & connectivity test",
|
||||
collect: "Batch Collect",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const zh = {
|
|||
umeSync: "UME同步",
|
||||
umeSyncDesc: "UME 告警同步、订阅与清单",
|
||||
network: "网络管理",
|
||||
networkDesc: "设备、拓扑、告警、WebCRT 与监控任务",
|
||||
networkDesc: "设备、告警、配置同步与监控任务",
|
||||
managedNe: "网元管理",
|
||||
managedNeDesc: "多厂商 SSH/Telnet 网元与连通性测试",
|
||||
collect: "批量采集",
|
||||
|
|
|
|||
|
|
@ -798,6 +798,52 @@ pre {
|
|||
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 {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
|
@ -2708,12 +2754,13 @@ pre {
|
|||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border: 1px solid #cbd5e1;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: #475569;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.app-main .network-nav__collapse-btn:hover,
|
||||
|
|
|
|||
|
|
@ -235,46 +235,43 @@ export function ConfigSyncPage() {
|
|||
|
||||
<div className="panel" style={{ marginBottom: 16 }}>
|
||||
<h3>{t("configSync.policyTitle")}</h3>
|
||||
<div className="filter-inline" style={{ flexWrap: "wrap", gap: 12 }}>
|
||||
<label>
|
||||
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />{" "}
|
||||
{t("configSync.enabled")}
|
||||
<div className="config-sync-policy-row">
|
||||
<label className="config-sync-policy-check">
|
||||
<input type="checkbox" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />
|
||||
<span>{t("configSync.enabled")}</span>
|
||||
</label>
|
||||
<label>
|
||||
{t("configSync.intervalDays")}{" "}
|
||||
<label className="config-sync-policy-field">
|
||||
<span>{t("configSync.intervalDays")}</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={365}
|
||||
value={intervalDays}
|
||||
onChange={(e) => setIntervalDays(Math.max(1, Number(e.target.value) || 1))}
|
||||
style={{ width: 72 }}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{t("configSync.concurrency")}{" "}
|
||||
<label className="config-sync-policy-field">
|
||||
<span>{t("configSync.concurrency")}</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={30}
|
||||
value={concurrency}
|
||||
onChange={(e) => setConcurrency(Math.max(1, Math.min(30, Number(e.target.value) || 1)))}
|
||||
style={{ width: 72 }}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{t("configSync.historyKeep")}{" "}
|
||||
<label className="config-sync-policy-field">
|
||||
<span>{t("configSync.historyKeep")}</span>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={30}
|
||||
value={historyKeep}
|
||||
onChange={(e) => setHistoryKeep(Math.max(0, Math.min(30, Number(e.target.value) || 0)))}
|
||||
style={{ width: 72 }}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{t("configSync.scope")}{" "}
|
||||
<label className="config-sync-policy-field">
|
||||
<span>{t("configSync.scope")}</span>
|
||||
<select
|
||||
value={scopeMode}
|
||||
onChange={(e) => setScopeMode(e.target.value === "selected" ? "selected" : "all")}
|
||||
|
|
|
|||
|
|
@ -781,7 +781,7 @@ export function NePage() {
|
|||
onClick={() =>
|
||||
openOrFocusModule({
|
||||
moduleId: "network",
|
||||
path: `/network/webcrt?ne_id=${encodeURIComponent(row.id)}`,
|
||||
path: `/webcrt?ne_id=${encodeURIComponent(row.id)}`,
|
||||
})
|
||||
}
|
||||
title={t("managedNe.openTerminal")}
|
||||
|
|
|
|||
|
|
@ -677,14 +677,14 @@ export function TopologyPage() {
|
|||
if (managedId) {
|
||||
openOrFocusModule({
|
||||
moduleId: "network",
|
||||
path: `/network/webcrt?ne_id=${encodeURIComponent(managedId)}`,
|
||||
path: `/webcrt?ne_id=${encodeURIComponent(managedId)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (umeId) {
|
||||
openOrFocusModule({
|
||||
moduleId: "network",
|
||||
path: `/network/webcrt?ne_id=${encodeURIComponent(umeId)}&source=ume`,
|
||||
path: `/webcrt?ne_id=${encodeURIComponent(umeId)}&source=ume`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export function NetworkConfigsPage() {
|
|||
{t("networkConfigs.view")}
|
||||
</button>
|
||||
<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 }}
|
||||
>
|
||||
WebCRT
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue