[OpenNMS/opennms] aeec63: NMS-19878: Add the Topology (Preview) page

Marshall Massengill via opennms-cvs <[email protected]>
Newsgroups gmane.network.opennms.cvs
Message-ID <OpenNMS/opennms/push/refs/heads/mm/NMS-19878-smoke/[email protected]>
  Branch: refs/heads/mm/NMS-19878-smoke
  Home:   https://github.com/OpenNMS/opennms
  Commit: aeec6349bd6a4ac13fd058960791a38d4d777bad
      https://github.com/OpenNMS/opennms/commit/aeec6349bd6a4ac13fd058960791a38d4d777bad
  Author: Marshall Massengill <[email protected]>
  Date:   2026-08-19 (Wed, 19 Aug 2026)

  Changed paths:
    M opennms-webapp-rest/src/main/webapp/WEB-INF/menu/menu-template.json
    M ui/CLAUDE.md
    M ui/eslint.config.js
    M ui/package.json
    M ui/packages/onms-ui/README.md
    M ui/packages/onms-ui/src/components/OnmsAutoComplete.vue
    M ui/packages/onms-ui/src/components/OnmsButton.vue
    A ui/packages/onms-ui/src/components/OnmsColorPicker.vue
    A ui/packages/onms-ui/src/components/OnmsContextMenu.vue
    M ui/packages/onms-ui/src/components/OnmsInputNumber.vue
    M ui/packages/onms-ui/src/components/OnmsMultiSelect.vue
    M ui/packages/onms-ui/src/components/OnmsSearchInput.vue
    A ui/packages/onms-ui/src/components/OnmsSelectButton.vue
    A ui/packages/onms-ui/src/components/OnmsSlider.vue
    M ui/packages/onms-ui/src/components/OnmsTable.vue
    A ui/packages/onms-ui/src/components/OnmsTieredMenu.vue
    A ui/packages/onms-ui/src/components/OnmsVirtualScroller.vue
    M ui/packages/onms-ui/src/index.ts
    M ui/packages/onms-ui/src/types.ts
    M ui/pnpm-lock.yaml
    A ui/src/components/Topology/TopologyCanvas.vue
    A ui/src/components/Topology/TopologyExplorePanel.vue
    A ui/src/components/Topology/TopologyInspector.vue
    A ui/src/components/Topology/TopologyPalette.vue
    A ui/src/components/Topology/ViewManager.vue
    A ui/src/components/Topology/deviceIcons.ts
    A ui/src/components/Topology/dragTypes.ts
    A ui/src/components/Topology/focus.ts
    A ui/src/components/Topology/layout.ts
    A ui/src/components/Topology/linkHints.ts
    A ui/src/components/Topology/nodeActions.ts
    A ui/src/components/Topology/nodeIds.ts
    A ui/src/components/Topology/severity.ts
    A ui/src/components/Topology/sources.ts
    A ui/src/containers/Topology.vue
    M ui/src/main/router/index.ts
    A ui/src/services/topologyService.ts
    A ui/src/stores/topologyStore.ts
    M ui/src/theme/opennms-preset.ts
    A ui/src/types/topology.ts
    A ui/tests/components/Topology/TopologyExplorePanel.test.ts
    A ui/tests/components/Topology/TopologyInspector.test.ts
    A ui/tests/components/Topology/ViewManager.test.ts
    A ui/tests/components/Topology/deviceIcons.test.ts
    A ui/tests/components/Topology/focus.test.ts
    A ui/tests/components/Topology/layout.test.ts
    A ui/tests/components/Topology/linkHints.test.ts
    A ui/tests/components/Topology/nodeActions.test.ts
    A ui/tests/components/Topology/nodeIds.test.ts
    A ui/tests/components/Topology/severity.test.ts
    A ui/tests/components/Topology/sources.test.ts
    A ui/tests/containers/Topology.test.ts
    M ui/tests/onms-ui/OnmsButton.test.ts
    A ui/tests/onms-ui/OnmsColorPicker.test.ts
    A ui/tests/onms-ui/OnmsContextMenu.test.ts
    M ui/tests/onms-ui/OnmsInputNumber.test.ts
    M ui/tests/onms-ui/OnmsMultiSelect.test.ts
    M ui/tests/onms-ui/OnmsSearchInput.test.ts
    A ui/tests/onms-ui/OnmsSelectButton.test.ts
    A ui/tests/onms-ui/OnmsSlider.test.ts
    M ui/tests/onms-ui/OnmsTable.test.ts
    A ui/tests/onms-ui/OnmsTieredMenu.test.ts
    A ui/tests/onms-ui/OnmsVirtualScroller.test.ts
    M ui/tests/onms-ui/exports.test.ts
    A ui/tests/services/topologyService.test.ts
    A ui/tests/stores/topologyStore.test.ts

  Log Message:
  -----------
  NMS-19878: Add the Topology (Preview) page

A Vue replacement for the Vaadin topology map, under Topology (Preview).

Hand-composed views: drag nodes from a palette of real OpenNMS nodes onto a
sigma.js canvas, move and multi-select them, draw links, add free-standing labels
and annotation shapes, undo and redo, then save the result as a named view.
Views persist through /api/v2/topology/views, which stores the canvas as an
opaque JSON document, so the service maps between that and the flat front-end
model. A view is bookmarkable, and the catalog is shared rather than per-user.

Discovered views: enlinkd's graphs served through /api/v2/graphs, grouped into
Layer 2 and Layer 3 with a variant picker rather than the eleven flat namespaces
the API reports, plus Path Outage as a derived source with a tiered hierarchy
layout, being a rooted tree rather than a mesh. Focus and Semantic Zoom Level
reduce a large graph to a node's neighborhood and both live in the URL. The source
menu is built from the API rather than hardcoded, so an operator's GraphML
topology or a bridged legacy provider appears without a UI change.

Node color follows alarm severity, polled in View mode and frozen in Edit mode so
the canvas does not repaint while arranging. Device icons derive from sysObjectId
the way the legacy map did, and a view can override them per node.

Selection drives an Inspector carrying the legacy map's Node and Technical
Details plus the operator's own etc/infopanel templates; a vertex that is not an
OnmsNode, such as an application or a service, is legible rather than showing a
bare id. An Explore panel below lists the view's alarms, nodes, and -- on the
application graph -- applications and perspective outages, with counts that
follow the selection.

Assisted composition: ghost links show discovered adjacencies between placed
nodes that have no link yet, and a neighbor tray offers a selected node's
discovered neighbors for placement.

Every PrimeVue call site goes through @opennms/onms-ui, which this extends with
five new wrappers and widens five existing ones, so the page holds no direct
primevue/* import. Dark mode applies the theme class to <html> so PrimeVue's own
component variables resolve, and the canvas-drawn colors CSS cannot reach are
derived from the active theme.


  Commit: 431c53d2ac86392692e6788b67a3fa0cceb662c0
      https://github.com/OpenNMS/opennms/commit/431c53d2ac86392692e6788b67a3fa0cceb662c0
  Author: Marshall Massengill <[email protected]>
  Date:   2026-08-19 (Wed, 19 Aug 2026)

  Changed paths:
    M ui/src/components/Topology/TopologyExplorePanel.vue
    M ui/src/components/Topology/TopologyInspector.vue
    M ui/src/components/Topology/focus.ts
    M ui/src/containers/Topology.vue
    M ui/src/main/App.vue
    M ui/src/main/index.html
    M ui/src/services/topologyService.ts
    M ui/src/stores/topologyStore.ts
    M ui/tests/components/Topology/TopologyExplorePanel.test.ts
    M ui/tests/components/Topology/TopologyInspector.test.ts
    M ui/tests/components/Topology/focus.test.ts
    M ui/tests/containers/Topology.test.ts
    M ui/tests/services/topologyService.test.ts
    M ui/tests/stores/topologyStore.test.ts

  Log Message:
  -----------
  NMS-19878: Make large discovered topologies usable in the preview map

Several faults compounded on a topology big enough to matter, and each one hid
the next.

Every id-filtered query built one FIQL clause per node, which on a 3454-vertex
view exceeded Jetty's requestHeaderSize (4000 bytes by default, covering the
whole request line and headers). The server answered 414 and each caller's catch
turned that into "no data": an uncolored canvas, no device glyphs, empty Explore
tabs. Queries are chunked by encoded length now, not by clause count -- counting
cannot work, since node.id== is five bytes longer per clause than id== and axios
percent-encodes = as %3D. Measured live, 150 node.id== clauses encode to 3011
bytes and pass while 200 encode to 4011 and answer 414. getNodeIconIds was never
chunked at all.

The large-graph gate tested whether a focus existed rather than how much would be
drawn, so stepping the zoom twice from its own suggested anchor rendered ~1700
and then all 3414 vertices -- seconds of blocking layout, which is what the gate
is for. It measures the rendered subgraph now, and offers the most-connected
vertex as a starting point rather than leaving the user to guess a node name.
Degree is structural on purpose: the API does carry each graph's defaultFocus,
but the enlinkd value behind it is a global highest-summed-ifSpeed node and
setDefaultVertex silently substitutes an arbitrary vertex when that node is
absent, with nothing in the response to tell the two apart.

Status and icons followed the whole placed-node set rather than what is on
screen, so a focused view kept the previous subgraph's severities -- default blue
in View mode, and indefinitely in Edit mode where the poll is stopped.

The page also sized itself with calc(100vh - 104px), a constant measured once
against the app shell that has to match the footer's rendered height. It was
quietly the only thing bounding the page, since .app-layout is min-height:100vh,
so a tall inspector grew the layout and pushed the footer off screen. The route
bounds the layout and takes its height from the grid row instead, with no
arithmetic.

Discovered structure is refetched on demand from the toolbar, rather than
polled: every enlinkd rescan interval defaults to 86400000 ms, and the button
only ever called refreshStatus, which recolors what is already drawn.

Tests assert encoded bytes rather than clause counts, which is the assertion that
was missing when a first attempt at the chunking shipped still broken.


  Commit: 172fc4432551026ad5b3dc6d1c0c53589b654cb9
      https://github.com/OpenNMS/opennms/commit/172fc4432551026ad5b3dc6d1c0c53589b654cb9
  Author: Marshall Massengill <[email protected]>
  Date:   2026-08-19 (Wed, 19 Aug 2026)

  Changed paths:
    M ui/src/components/Topology/TopologyCanvas.vue
    M ui/src/components/Topology/TopologyInspector.vue
    M ui/src/components/Topology/deviceIcons.ts
    A ui/src/components/Topology/search.ts
    M ui/src/containers/Topology.vue
    M ui/src/services/topologyService.ts
    M ui/src/stores/topologyStore.ts
    M ui/src/types/topology.ts
    M ui/tests/components/Topology/TopologyInspector.test.ts
    M ui/tests/components/Topology/deviceIcons.test.ts
    A ui/tests/components/Topology/search.test.ts
    M ui/tests/containers/Topology.test.ts
    M ui/tests/services/topologyService.test.ts

  Log Message:
  -----------
  NMS-19878: Broaden search, and use what providers say about their graphs

Search matched a node's label or its id, so an operator who knows a device by
address could not find it, and the box only existed on discovered views. Every
vertex already arrives with its provider properties, so one matcher covers what
the old map needed a search provider apiece for: IpLike via the ipAddress
enlinkd sets, plus an application's or a GraphML author's own attributes.
Suggestions show the matched value, since a hit on an address is otherwise
indistinguishable from a hit on a name.

The box renders on custom views too, where picking a result selects the node and
pans to it rather than focusing, there being no subgraph to reduce.

Category was the old map's common way in, and the one search provider whose data
the graph payload does not carry. A category hit resolves to the canvas ids of
its members that are vertices of the current graph and selects them, which the
canvas already highlights and the Explore panel already filters by. Categories
come off the node payload because v2 /nodes cannot filter on them: its
categories alias join is commented out in NodeRestService, so any
category-prefixed filter answers 500 from Hibernate, and /api/v2/categories is a
404. The fetch is keyed on the node id set, dedupes concurrent callers, caches
only success, and a generation counter stops a slow query landing on top of a
newer one.

Three providers were describing their own topology and the UI discarded most of
it. VMware rendered every vertex as an identical circle, because our icon map is
keyed on sysObjectId and its vmware.* keys matched nothing; the Vaadin map drew
five distinct icons here. Two plumbing faults sat behind that: the discovered
addNode never passed the vertex's icon, and icon resolution sat inside the
resolvedNodeId branch, so a datacenter, network or datastore could never get one.
GraphML's preferred-layout was ignored, so a layer declaring Hierarchy Layout
drew force-directed, and its edge labels were dropped wholesale -- restoring them
showed why, since enlinkd and VMware label every edge with its own
namespace-qualified id.

Selecting a link showed only its endpoints. The old map colored the line red when
an interface was down and left the operator to work out which end; naming them is
the more useful half and needs no alarm configuration. enlinkd reports the
ifIndex inside the port's display string and its interface URL rather than as a
field, which is why an earlier reading concluded no API exposed it per edge.
Extracting it found two live bugs: parseNeighborNodeId took the first URL in a
record, but enlinkd puts the local port's first, so every link resolved back to
its own node and was dropped as a self-link; and the seen set was shared across
protocols, so whichever parsed first discarded the rest, including the ports and
ifIndex this exists to show.


  Commit: 39c8011d8af8e1752816f85dc3aba6383d3eff59
      https://github.com/OpenNMS/opennms/commit/39c8011d8af8e1752816f85dc3aba6383d3eff59
  Author: Marshall Massengill <[email protected]>
  Date:   2026-08-19 (Wed, 19 Aug 2026)

  Changed paths:
    M ui/src/components/Topology/TopologyCanvas.vue
    M ui/src/components/Topology/TopologyExplorePanel.vue
    M ui/src/components/Topology/TopologyInspector.vue
    A ui/src/components/Topology/TopologyLocationMap.vue
    M ui/src/components/Topology/TopologyPalette.vue
    M ui/src/components/Topology/deviceIcons.ts
    A ui/src/components/Topology/interfaceState.ts
    M ui/src/components/Topology/search.ts
    M ui/src/components/Topology/severity.ts
    M ui/src/containers/Topology.vue
    M ui/src/main/App.vue
    M ui/src/main/index.html
    M ui/src/services/geolocationService.ts
    M ui/src/services/topologyService.ts
    M ui/src/stores/topologyStore.ts
    M ui/src/styles/onms-theme.scss
    M ui/tests/components/Topology/TopologyInspector.test.ts
    A ui/tests/components/Topology/TopologyLocationMap.test.ts
    A ui/tests/components/Topology/interfaceState.test.ts
    M ui/tests/components/Topology/severity.test.ts
    A ui/tests/containers/AppLayoutClasses.test.ts
    M ui/tests/containers/Topology.test.ts
    M ui/tests/services/topologyService.test.ts
    M ui/tests/stores/topologyStore.test.ts

  Log Message:
  -----------
  NMS-19878: Map node locations, theme the canvas, and fix what review found

Restores the small map the Vaadin info panel put beside a selected node, when an
operator has filled in its asset coordinates. Nearly free once the data path was
checked: the UI already ships leaflet, and /api/v2/nodes/{id} carries
assetRecord.latitude/longitude, which the inspector already fetches. Titled
"Geographic Location" because Node Details already has a Location row meaning the
monitoring location. Absent coordinates, half a pair, and 0,0 all render nothing
-- Number(null) is 0, not NaN, and the API sends unset asset fields as null, so a
node with only a longitude was being plotted on the equator. The marker passes an
explicit icon, since Vite inlines marker-icon.png as a data URI and Leaflet's
default icon locates its images by parsing a CSS url.

Interface state on a link, the half of edge status that was designed and not
built: the raw IF-MIB ifOperStatus named rather than reduced to up/down, because
lowerLayerDown and down mean different things and up-values/down-values are
per-package config the API does not expose. Shown with its provenance, since
provisiond's node scan writes these columns daily and the SNMP Interface Poller
-- off by default, and needing ENABLE_POLLING per interface -- is the only thing
that makes them fresher.

Theming. The topology accent was a hardcoded literal in 26 places that never
followed the theme, measuring 3.07:1 against the dark canvas and 2.76:1 on a dark
panel, below the 3.0:1 floor WCAG 1.4.11 sets for non-text UI components. It is a
token pair now, and the nine persisted literals collapsed to DEFAULT_NODE_COLOR,
which stays theme-independent on purpose: a view composed in dark mode must not
save different colors from the same view in light mode. That separation is what
let link emphasis be fixed -- because the base color cannot follow the theme, the
light theme's highlight was the *same color* as the base and emphasis was carried
by fading everything else, which erased the other links at 1.20:1. Links are
drawn neutral now and the accent means "this one". Export follows the canvas
rather than a hardcoded white, which flattened a dark view while its labels kept
their dark-theme color at 1.29:1.

Defects an adversarial review found, all confirmed before fixing: a 500px
min-height on the canvas and its wrapper gave the page an unshrinkable floor, so
a viewport under ~1190px overflowed; the Explore panel refetched with no in-flight
guard, so a zoom step overlapped the status poll and the later response won; the
inspector assigned node detail after its await with no staleness check, so
clicking one node then another could leave the first's detail under the second;
IS-IS reports its index as a number with no port string and bridge keeps its far
ends in a nested array, so neither resolved an ifIndex; the location map never
called invalidateSize on resize; resolveDeviceIcon ran per node per frame,
linear-scanning ~90 keys on a miss that 3414 of 3454 vertices take; a cleared
alarm stuck and painted a node grey; and getNeighborsFor cached an empty result,
so one transient error hid a node's links for the session.

The search box carries the focused node, so the hop stepper reads as "2 hops from
this node". The body margin reset is scoped to this route rather than applied to
<body>, which had also stripped every other page's gutters and restyled every
teleported PrimeVue overlay.

Adds the tests review found missing: gate re-arming per load, the "Render all"
click path, App.vue's route-scoped classes, and getNodeCategories, which was
mocked everywhere and never exercised.


Compare: https://github.com/OpenNMS/opennms/compare/aeec6349bd6a%5E...39c8011d8af8

To unsubscribe from these emails, change your notification settings at https://github.com/OpenNMS/opennms/settings/notifications


_______________________________________________
Please read the OpenNMS Mailing List FAQ:
http://www.opennms.org/wiki/index.php?page=MailingListFaq
opennms-cvs mailing list

To *unsubscribe* or change your subscription options, see the bottom of this page:
https://lists.sourceforge.net/lists/listinfo/opennms-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.