diff --git a/web/src/features/managed-ne/NePage.tsx b/web/src/features/managed-ne/NePage.tsx index cc7fc78..bb3fd6a 100644 --- a/web/src/features/managed-ne/NePage.tsx +++ b/web/src/features/managed-ne/NePage.tsx @@ -21,7 +21,8 @@ import { } from "../../services/api"; import { HelpHint } from "../../components/HelpHint"; import { ListPager } from "../../components/ListPager"; -import { HopProxyFields, emptyHopProxyFields, type HopProxyFieldsState } from "./components/HopProxyFields"; +import { HopProxyFields } from "./components/HopProxyFields"; +import { emptyHopProxyFields, type HopProxyFieldsState } from "./lib/hopProxy"; import { AppModalShell } from "../../components/ui/AppModalShell"; import { FieldSelect } from "../../components/ui/FieldSelect"; import { queryKeys } from "../../constants/queryKeys"; @@ -145,19 +146,14 @@ export function NePage() { }, }); - useEffect(() => { - if (!connectDetailRow) return; - const updated = listQuery.data?.items?.find((x) => x.id === connectDetailRow.id); - if (!updated) return; - if ( - updated.connect_status !== connectDetailRow.connect_status || - updated.connect_message !== connectDetailRow.connect_message || - updated.connect_detail !== connectDetailRow.connect_detail || - updated.connect_tested_at !== connectDetailRow.connect_tested_at - ) { - setConnectDetailRow(updated); - } - }, [listQuery.data, connectDetailRow]); + // The detail dialog follows the list's latest copy of the row (status polling). + const connectDetailLive = useMemo( + () => + connectDetailRow + ? (listQuery.data?.items?.find((x) => x.id === connectDetailRow.id) ?? connectDetailRow) + : null, + [listQuery.data, connectDetailRow], + ); const total = listQuery.data?.total ?? 0; const pages = pageCount(total, pageSize); @@ -341,34 +337,30 @@ export function NePage() { ); }; - if (neId) { - void (async () => { - try { - const row = await fetchManagedNeById(neId); - openEdit(row); - } catch (err) { - showError(managedNeErrorText(err, t)); - } finally { - clearDeepLink(); + void (async () => { + try { + if (neId) { + openEdit(await fetchManagedNeById(neId)); + return; } - })(); - return; - } - - const name = String(searchParams.get("name") || "").trim(); - const ip = String(searchParams.get("ip_address") || "").trim(); - const vendorRaw = String(searchParams.get("vendor") || "").trim(); - const base = emptyManagedNeForm(); - const vendor = vendorRaw || base.vendor; - setEditing(null); - setFormSeed({ - name, - vendor, - device_type: deviceTypeForVendor(vendor), - ip_address: ip, - }); - setModalOpen(true); - clearDeepLink(); + const name = String(searchParams.get("name") || "").trim(); + const ip = String(searchParams.get("ip_address") || "").trim(); + const vendorRaw = String(searchParams.get("vendor") || "").trim(); + const vendor = vendorRaw || emptyManagedNeForm().vendor; + setEditing(null); + setFormSeed({ + name, + vendor, + device_type: deviceTypeForVendor(vendor), + ip_address: ip, + }); + setModalOpen(true); + } catch (err) { + showError(managedNeErrorText(err, t)); + } finally { + clearDeepLink(); + } + })(); }, [searchParams, setSearchParams, showError, t]); const toggleSelect = (id: string) => { @@ -386,7 +378,7 @@ export function NePage() { const stats: ManagedNeStats | undefined = statsQuery.data; const statTags: string[] = stats?.tags ?? []; - const perTag = stats?.per_tag ?? {}; + const perTag = useMemo(() => stats?.per_tag ?? {}, [stats]); const tagCardItems = useMemo(() => { if (!stats) return []; const items: Array<{ key: string; title: string; total: number; by_status: Record }> = []; @@ -525,7 +517,6 @@ export function NePage() { {stats ? ( <> -

{t("managedNe.stats.cardFilterHint")}

{tagCardItems.map((x) => ( diff --git a/web/src/features/topology/lib/constants.ts b/web/src/features/topology/lib/constants.ts index 5f28f53..54bb3b5 100644 --- a/web/src/features/topology/lib/constants.ts +++ b/web/src/features/topology/lib/constants.ts @@ -4,7 +4,7 @@ export const WORLD_MAP_ENABLED = false; export const LAST_LEAF_KEY = "netx.topology.lastLeafViewId"; export const TREE_EXPAND_KEY = "netx.topology.treeExpanded"; export const SIDEBAR_WIDTH_KEY = "netx.topology.sidebarWidth"; -export const SIDEBAR_WIDTH_DEFAULT = 260; +export const SIDEBAR_WIDTH_DEFAULT = 280; export const SIDEBAR_WIDTH_MIN = 180; export const SIDEBAR_WIDTH_MAX = 560; diff --git a/web/src/features/ume/components/UmeCliConnectPanel.tsx b/web/src/features/ume/components/UmeCliConnectPanel.tsx index d8195df..dccdad3 100644 --- a/web/src/features/ume/components/UmeCliConnectPanel.tsx +++ b/web/src/features/ume/components/UmeCliConnectPanel.tsx @@ -12,7 +12,8 @@ import { fetchUmeNe, postUmeConnectTest, } from "../../../services/api"; -import { HopProxyFields, emptyHopProxyFields, type HopProxyFieldsState } from "../../managed-ne/components/HopProxyFields"; +import { HopProxyFields } from "../../managed-ne/components/HopProxyFields"; +import { emptyHopProxyFields, type HopProxyFieldsState } from "../../managed-ne/lib/hopProxy"; import { HelpHint } from "../../../components/HelpHint"; import { FieldSelect } from "../../../components/ui/FieldSelect"; import { queryKeys } from "../../../constants/queryKeys"; diff --git a/web/src/i18n/locales/en/managedNe.ts b/web/src/i18n/locales/en/managedNe.ts index 24e07f7..fdab1d8 100644 --- a/web/src/i18n/locales/en/managedNe.ts +++ b/web/src/i18n/locales/en/managedNe.ts @@ -44,7 +44,6 @@ const managedNe = { loadingIds: "Loading…", noMatchingNe: "No NEs in the selected scope", scopeCount: "Applies to {{n}} NEs", - cardFilterHint: "Click a card to filter the list below by tag", }, col: { name: "Name", diff --git a/web/src/i18n/locales/zh/managedNe.ts b/web/src/i18n/locales/zh/managedNe.ts index 17fb12c..36198e3 100644 --- a/web/src/i18n/locales/zh/managedNe.ts +++ b/web/src/i18n/locales/zh/managedNe.ts @@ -43,7 +43,6 @@ const managedNe = { loadingIds: "加载中…", noMatchingNe: "所选范围内没有网元", scopeCount: "将作用于 {{n}} 台网元", - cardFilterHint: "点击卡片可按标签筛选下方列表", }, col: { name: "名称", diff --git a/web/src/styles/pages/ne-ume-system.css b/web/src/styles/pages/ne-ume-system.css index 70b26ab..bc453c3 100644 --- a/web/src/styles/pages/ne-ume-system.css +++ b/web/src/styles/pages/ne-ume-system.css @@ -1280,3 +1280,14 @@ button.ne-tag-card.is-active { padding: 8px 10px; background: rgb(var(--nx-accent-rgb) / 0.06); } + +/* NE list: row actions stay on one line (the table scrolls sideways if it must) + instead of wrapping and doubling the row height. */ +.ne-page .pt-list-table .pt-list-actions { + flex-wrap: nowrap; + gap: 6px; +} + +.ne-page .pt-list-table td:has(> .pt-list-actions) { + white-space: nowrap; +} diff --git a/web/src/styles/pages/topology.css b/web/src/styles/pages/topology.css index d1eb8a3..b4c6950 100644 --- a/web/src/styles/pages/topology.css +++ b/web/src/styles/pages/topology.css @@ -2,7 +2,7 @@ /* ---- Topology — dark sidebar + canvas (shared chrome tokens) ---- */ .topo-page { - --topo-sidebar-w: 260px; + --topo-sidebar-w: 280px; display: grid; grid-template-columns: var(--topo-sidebar-w) 1fr; gap: 12px; @@ -1243,10 +1243,11 @@ flex-direction: column; gap: 2px; padding: 2px 0; - /* Allow deep indent + actions to extend; parent scroll shows a horizontal bar. */ - width: max-content; - min-width: 100%; - max-width: none; + /* Rows fit the sidebar: long names ellipsize (full name on hover), counts + stay visible on the right — no horizontal scrollbar. */ + width: 100%; + min-width: 0; + max-width: 100%; box-sizing: border-box; } @@ -1254,7 +1255,7 @@ flex: 1 1 auto; min-height: 0; min-width: 0; - overflow-x: auto; + overflow-x: hidden; overflow-y: auto; border: 1px solid rgba(148, 163, 184, 0.22); border-radius: 6px; @@ -1432,10 +1433,10 @@ } .topo-region-list .topo-map-list__title { - min-width: max-content; + min-width: 0; flex: 0 1 auto; - overflow: visible; - text-overflow: clip; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 650; @@ -1449,7 +1450,8 @@ .topo-region-list .topo-map-list__count { flex: 0 0 auto; - margin-left: 4px; + margin-left: auto; + padding-left: 6px; padding: 0; border-radius: 0; font-size: 11px;