NMS-20126: dashboard parity panels and default layout - #8722
Open
joseanesONMS wants to merge 50 commits into
Open
NMS-20126: dashboard parity panels and default layout#8722joseanesONMS wants to merge 50 commits into
joseanesONMS wants to merge 50 commits into
Conversation
…stence backend) Adds a configurable, PrimeVue-based Vue 3 dashboard under /ui (route /dashboard) to begin replacing the legacy JSP homepage, plus the REST endpoint to persist its layout. Frontend (ui/): - Panel framework: registry, Pinia store, default layout, PanelFrame chrome (PrimeVue Panel), and a sample panel. Panels receive resolved filter / timeframe / refresh contracts. - DashboardGrid uses grid-layout-plus for drag + resize (edit mode only); geometry flows grid -> store, add/remove/collapse flow store -> grid. - Toolbar: global timeframe, refresh interval + pause (NMS-4404), add panel, save/edit; per-panel collapse/rename/remove. - Global filter control (NMS-10507): surveillance categories + IP match. - dashboardService talks to /api/v2/dashboard/system (404 -> built-in default). Backend (opennms-webapp-rest): - DashboardRestApi / DashboardRestService: GET/PUT /api/v2/dashboard/system, persisting one system-wide JSON layout document via JsonStore. Addresses NMS-11946 (collapse/rearrange/hide) and lays groundwork for NMS-4433 (new panel types). Future: per-user / named dashboards reuse the same layout document by varying its scope.
…rview, nodes with alarms) Replaces the placeholder default layout with three panels matching the legacy homepage boxes: - Notifications: outstanding-notice counts from /rest/notifications/summary. - Status Overview: Chart.js doughnut of alarm counts by severity, read from /api/v2/alarms totalCount per severity (FIQL alarm.severity==). - Nodes with Pending Alarms: alarms grouped by node with count + max severity. Adds a shared severity util (ordering, labels, OpenNMS-standard colors) and a notificationService. Panels refetch on the global refresh tick.
- Status Overview now shows BOTH legacy doughnuts (alarms + outages), sourced
from /api/v2/status/summary/nodes/{alarms,outages} (nodes grouped by unacked
alarms / current outages), replacing the prior alarms-only per-severity count.
- New News Feed panel from /api/v2/newsfeed (title/link/short description/tags).
Adds statusService and newsfeedService; both panels added to the default layout.
- Pending Situations: alarms filtered isSituation==true (/api/v2/alarms), with severity, affected-node/alarm counts. - Nodes with Service Outages: currently-open outages from /api/v2/outages (FIQL ifRegainedService==epoch), node + service name. Adds outageService and situationService; refactors the default layout into a three-column arrangement (situations/alarms/outages, status overview, notifications/news) via a small panel() helper.
Leaflet/OSM map (vue-leaflet) replicating the legacy homepage map: geolocated nodes (/api/v2/nodes assetRecord) with markers colored by each node's highest alarm severity (derived from /api/v2/alarms). Handles grid resize via ResizeObserver -> invalidateSize. Added to the registry + default layout.
Match the legacy homepage proportions: 3 / 6 / 3 of the 12-col grid (was even 4/4/4). Left = situations/alarms/outages, center (wide) = status overview + map, right = notifications/news. Individual panels remain resizable via the grid.
Replicates the legacy "Availability Over the Past 24 Hours" categories box from GET /rest/availability (RTC category data: outage-text, availability-text, availability-class). Table of category / outages / availability with an Overall Service Availability total row; availability colored by class. No new backend needed. Added to registry + default layout (center column, under Status Overview).
…s) panels Both replicate the legacy summary boxes from the existing status REST (/api/v2/status/business-services and /status/applications) — no new backend needed; the list-with-severity endpoints already exist in StatusRestService. Shared StatusListPanel renders name + severity badge, filtered to problem severities (WARNING+). Added to registry + left column of the default layout.
…fullscreen - Add Quick Search (node id/label/IP -> element/nodeList.htm), and Resource Graphs / KSC Reports launcher panels (link to legacy chooser pages; full typeahead is a follow-up). - Default layout now mirrors the legacy homepage column order/positions for easy comparison: left = situations/alarms/outages/BSM/apps/news; center = status/availability/map; right = notifications/resource-graphs/KSC/quick-search. - Toolbar: title tooltips on the timeframe + refresh selects (and pause), and a Full screen button (Fullscreen API on the dashboard container) for NOC displays.
…titles) - PanelFrame: make the PrimeVue Panel content wrappers flex to a bounded height so fill-height panels (map, charts) size correctly instead of overflowing. - Regional Status: always renders the map (even with no nodes); remembers pan + zoom across reloads via localStorage; empty-state shown as a small pill. - Status Overview: thicker donuts (cutout 55%) that fit the box (no longer cut). - Resource Graphs / KSC Reports: drop the redundant in-panel title (the panel header already shows it).
- web.xml: Permissions-Policy was fullscreen=() which blocked the Fullscreen API; changed to fullscreen=(self). Toolbar fullscreen toggle now also falls back to a CSS "maximize" if the API is unavailable. - DashboardGrid: add bottom padding so the app's fixed copyright bar no longer cuts the last panels. - Dashboard: global style so PrimeVue overlays (teleported to <body>) use the app sans-serif font instead of the serif default (fixes combo-box dropdown font). - Status Overview: donuts resize with the panel (ResizeObserver) and are clipped to the box.
…panels - Per-panel options dialog (gear): height mode + panel-type-specific settings. - Height mode per panel: 'fixed' (fixed grid height + scrollbar) or 'auto' (PanelFrame measures natural content height; DashboardGrid fits the cell, so empty list panels shrink to a couple of lines). Auto panels aren't manually resizable and their height isn't persisted. Registry defaults match the legacy homepage: News Feed / Availability / Status Overview / Regional map = fixed, all the list panels = auto. - New panels: Notes (free text in options) and HTML Content (iframe to a URL; same-origin per the frame-src CSP).
…tion) grid-layout-plus doesn't recompact on programmatic height changes, so auto-height resizes (and add/remove) could leave panels overlapping. Add our own vertical compaction (place each item at the lowest non-colliding y in its columns) run after auto-height changes and reconcile.
…TML URL validation - Fixed panels now actually bound + scroll: the PrimeVue 4 chain has an extra .p-panel-content-wrapper between container and content that wasn't flexed — add it. This fixes News Feed/Availability scrollbars AND the Regional map (the map now gets a real height and renders tiles even with no nodes). - Dashboard scrolls internally (flex column; grid is the scroll region) and the content chain gets a definite height via :has(#dashboard-root), so a tall dashboard no longer overflows past the app footer/copyright bar. - HTML Content options: validate the URL — external (cross-origin) URLs are rejected with an explanation (blocked by frame-src 'self' CSP) before applying.
… whitespace) Auto panels were clamped to the registry minSize.h, leaving whitespace under short content (and reserving space visibly in edit mode). Use min-h=1 for auto-height panels so the cell hugs the measured content.
…meframe) New 'topn' panel: ranks by a selectable KPI with sort direction and N (options dialog). Default KPI = Node Response Time (ICMP), descending, N=5. Data via the measurements API (POST /rest/measurements): enumerate matching resources from /rest/resources, query one AVERAGE bucket over the resolved timeframe, sort, take N. KPI registry (topnService) is extensible for more KPIs.
A single range-sized RRD bucket returns NaN; query at ~range/1000 (min 5min) and average the series for the aggregate.
- PanelFrame now wraps the PrimeVue Panel in a real <div> (frameRef) and measures that for auto-height — the component ref didn't reliably expose a DOM node, so auto panels never shrank (the leftover whitespace). Auto panels now fit content. - Panel surface/header/text/border use Feather theme variables (--feather-elevation-background-2 / -primary-text-on-surface / -border-on-surface), which adapt under .open-dark — so panels are legible in dark mode instead of white-with-invisible-text.
…tespace) Reduce PrimeVue panel header/content padding so short auto panels (Resource Graphs, KSC, etc.) don't round up to an extra grid row, and run a vertical compaction pass shortly after mount so columns stack tight once panels report their auto heights.
…acy->new button - DashboardGrid: compaction now reassigns the layout with FRESH item objects (and vertical-compact enabled) so grid-layout-plus actually re-renders compacted positions — removes the leftover gaps in saved layouts (e.g. Resource Graphs/KSC). - Per-panel "Shade rows by severity" option (legacy-style) for Pending Situations, Nodes with Pending Alarms, and Availability — rows tinted by severity. - Legacy homepage: "Try the new Dashboard (beta)" button below the Quick Search box (links to /opennms/ui/#/dashboard).
…ypes backend/combo - DashboardGrid: grid-layout-plus only re-renders when the layout ARRAY ref/length changes (watch on [layout, length]); it ignored in-place h/y mutations. compactLayout now ALWAYS reassigns fresh item objects, so auto-height + compaction truly apply (this was why the whitespace "did nothing"). - Backend: DashboardRestService gains GET /api/v2/dashboard/service-types (id+name) via ServiceTypeDao; rebuilt + deployed the webapp-rest jar. - Quick Search: "Providing service" combo populated from that endpoint, posting the service id to element/nodeList.htm (matches the legacy box).
…dup availability total Spacing (real fix): switch the grid to pixel units — row-height=1, vertical margin=0, h measured in px. Auto panels size to exactly their content height plus a 12px gap (was rounding up to the next ~56px row, which caused the persistent whitespace). Fixed panels fill the cell minus that 12px gap. Old/default layouts (h in ~44px row units) are auto-converted to px on load. Linking parity with the legacy front page: - Panel titles deep-link (Situations/Alarms -> alarm/list.htm, Outages -> outage/list.htm, Applications -> application/index.jsp, Availability -> rtc/index.jsp, Notifications -> notification/index.jsp, Resource Graphs -> graph/index.jsp, KSC -> KSC/index.jsp). Plain text while in edit mode so the header stays a drag handle. - Availability category names link to rtc/category.jsp (outage detail). Availability: render the REST "Total" section's "Overall Service Availability" row as the bold total instead of appending our own computed row (removes the duplicate). Legacy homepage "Try the new Dashboard" button: blue -> light gray.
- Business Services panel title now links to the BSM topology view (topology?provider=Business Services&layout=Hierarchy Layout&szl=1). - Status Overview donut legends read the Feather text color so labels are legible on the dark surface (Chart.js' default gray was unreadable); a MutationObserver recolors them live when the .open-dark theme is toggled.
…the timeframe New 'metric-chart' panel type: Chart.js line chart of a single metric on a single entity over the panel's resolved timeframe. Entity and metric are single-selects in the panel options (entity list = entities with data for the chosen metric); defaults: localhost / Node Response Time (ICMP). Reuses the Top-N KPI registry and measurements data path (listKpiSources extracted from queryTopn).
Registry 'hidden' flag: the panel stays registered (renders if a layout references it, handy for framework debugging) but is no longer offered in production via Add Panel.
store.applyFactoryDefault() swaps in createDefaultLayout() (legacy homepage parity incl. the Regional Map) and marks dirty; persists on Save. Fixes the gap where a saved layout always shadowed the built-in default with no way back (store.reset() only reloads the saved doc).
…parity)
Clicking a severity slice navigates to the legacy severity-filtered node
list (status/index.jsp?type=nodes&strategy={alarms|outages}&severityFilter=),
matching the original homepage status-box onclick; pointer cursor on hover.
# Conflicts: # ui/package.json # ui/pnpm-lock.yaml
FeatherDS is gone from the UI stack: the SORT enum import moves to its vendored home in @/types, and the panel styling swaps the feather CSS variables for the PrimeVue tokens the rest of /ui uses, including the computed-style reads that color the Chart.js legends. grid-layout-plus is the only dependency develop did not already carry.
Adds the missing automated coverage: store behavior (load/save/dirty tracking, panel operations, geometry sync, override resolution), panel registry and default-layout consistency, timeframe range math, severity helpers, the service fallback paths, the global refresh timer, and a REST integration test for the layout document lifecycle and service types. The layout endpoint gets the security it implied but never enforced: every user can read the system dashboard, only admins can save it — enforced by Spring Security rules and an in-code check, and covered by the integration test.
A stray symlink to a local worktree's node_modules was committed with the base framework change; it makes the CI pnpm install fail with ENOTDIR when it tries to create the real node_modules directory.
…t compaction fix
Move the dashboard framework chrome off direct PrimeVue onto the
@opennms/onms-ui Onms-* wrappers (NMS-20081 seam), so the framework
passes the no-direct-primevue lint rule. PanelFrame's edit icons move
into the panel header slot since OnmsPanel forwards only that slot.
Add an auto-fit ("squeeze") layout option persisted on the layout doc
and toggleable in edit mode: panels pack up and down against their
neighbours, or stay free-form. Fix the auto-height race where an async
layout reload reset measured panel heights back to their authored
placeholder and left gaps — measured heights are now cached and reused
across rebuilds, so collapse/expand and initial load pack tightly.
The panels that reproduce the legacy homepage — Pending Situations, Nodes with Pending Alarms / Service Outages, Status Overview (severity donuts), Availability, Regional Status map, Business Services, Applications, Notifications, News Feed, Resource Graphs, Graph Collections, Quick Search — plus the populated default layout that arranges them like the front page. Default-layout heights match the measured legacy panel heights (center column and News Feed) so the new page occupies about the same space as the old one. The Status Overview donut centre text is drawn on-canvas, proportional and vertically centred, with a slightly thinner ring. The global filter constrains the node-scoped panels; alarm parity matches the legacy boxes (unacknowledged, warning-and-above); the news feed honours opennms.newsFeedPanel.show=false with no outbound request.
joseanesONMS
force-pushed
the
jira/NMS-20126-parity-panels
branch
from
August 3, 2026 21:12
ea8c5be to
9f8ef83
Compare
Parity with the legacy node-alarm-summary box: the alarm count now links to the node's unacknowledged alarm list (alarm/list.htm ... filter=node), so each row has both a node link and an alarms link, not just the node.
…hboard-base # Conflicts: # ui/pnpm-lock.yaml
# Conflicts: # ui/package.json # ui/pnpm-lock.yaml
# Conflicts: # ui/pnpm-lock.yaml
The develop refresh brought stricter stylistic eslint rules that the dashboard test files predate; build-ui was red on 17 auto-fixable object-curly-spacing/arrow-parens/brace-style errors. lint:fix output only; the 52 dashboard tests still pass.
…nto jira/NMS-20126-parity-panels # Conflicts: # ui/tests/stores/dashboardStore.test.ts
joseanesONMS
changed the base branch from
jira/NMS-19851-dashboard-base
to
develop
August 31, 2026 14:55
The dashboard base (#8721) landed as a squash, so the shared Dashboard files show add/add against this branch; develop's copies are byte-identical to the base content this branch already merged, so the branch's versions win. !smoke
marshallmassengill
requested changes
Aug 31, 2026
marshallmassengill
left a comment
Contributor
There was a problem hiding this comment.
- AvailabilityPanel.vue:56 — filters RTC categories (etc/categories.xml: "Network Interfaces", "Web Servers") against surveillance categories from /rest/categories ("Routers", "Servers"). Two namespaces, no stock overlap, so any global filter selection blanks the panel.
- statusService.ts:62 — no limit sent, so QueryParameters.DEFAULT_LIMIT = 10 applies with no severity ordering, and the problem-severity filter then runs on those 10 rows. Shows "no pending alarms" while problems sit past row 10; the endpoint accepts severityFilter server-side.
- KscReportsPanel.vue:27 — GETs KSC/index.jsp?report=, but that page's filter is a client-side ng-model and reads no report param, so the search silently does nothing. Legacy used a rest/ksc typeahead → KSC/customView.htm?type=custom&report=.
I would also suggest that if we are creating "new" stuff for KSC, we should use the new name for them and not reference them as KSC.
- situationService.ts:31 — situationAlarmCount isn't a field on AlarmDTO (it has relatedAlarms + affectedNodeCount), so rows show "3 nodes" where legacy showed "affecting 3 nodes having 7 alarms".
- RegionalMapPanel.vue:92 — 2000 nodes + 2000 alarms per refresh tick, unordered and unfiltered; silent truncation both ways, and props.filter is ignored while the panel ships in the default layout.
- RegionalMapPanel.vue:53 — hardcoded OSM tile URL; geolocationStore.fetchUserDefinedTileProvider() already resolves the configured gwt.openlayers.url.
- statusService.ts:30 (+ situation/outage/newsfeed) — errors caught into [] and rendered as all-clear, inconsistent with availabilityService's deliberate null and notificationService's false in the same PR.
- defaultLayout.ts:28 — default heights converted to pixels but addPanel still writes rows; DashboardGrid.toPx's h <= 40 magnitude heuristic is the only thing keeping both working, and 56 is now duplicated.
- defaultLayout.ts:71 — newsfeed panel keeps its 9 rows when opennms.newsFeedPanel.show=false; legacy omitted the box.
- NotificationsPanel.vue:44 — count is teamUnacknowledgedCount (excludes you), link lists all unacknowledged.
- ResourceGraphsPanel.vue:38 — title promises graphs, form goes to the node list; legacy went to graph/chooseresource.jsp.
- NewsFeedPanel.vue:31 — safeLink resolves a missing link against the app origin instead of returning #.
- NodesWithAlarmsPanel.vue:106 — per-node counts only reflect the first 500-alarm page.
- RegionalMapPanel.vue:47 — saved pan/zoom key isn't per-panel.
Not sure how doable that KSC one is but if we're doing this then we might as well address it here.
Correctness: the availability panel no longer filters RTC categories by surveillance-category names (disjoint namespaces; any selection blanked it); the status lists filter and order by severity server-side instead of paging at the server's default limit and filtering the first ten rows; situations derive their alarm count from relatedAlarms (AlarmDTO has no situationAlarmCount); nodes-with-alarms reads the v1 alarm-summaries endpoint — the legacy source, exact server-side counts — narrowed by the dashboard filter resolved to node ids. Interactions: the KSC search box becomes a Graph Collections panel (new name, per review) with a real typeahead over rest/ksc that opens KSC/customView.htm by report id — index.jsp ignores a report parameter; the resource-graphs panel is an honest launcher into the Vue graphs browser (graph/chooseresource.jsp no longer exists); the notifications team line counts what its link lists. Fidelity and robustness: the regional map honors the dashboard filter, severity-orders its capped alarm fetch, uses the configured tile provider, and keys its saved pan/zoom per panel; a disabled news feed omits the panel from the default layout as the legacy homepage did, and a missing feed link renders as # instead of the app origin; the status/situation/outage/newsfeed services return null on failure and every panel shows an error line instead of an all-clear; the layout row size has one source of truth (ROW_PX) with addPanel and the default both writing pixels.
Contributor
Author
|
@marshallmassengill all 14 addressed in
64 tests green (10 files, incl. new coverage for the query contracts, the disabled-feed default, and the Graph Collections navigation); full eslint + |
marshallmassengill
requested changes
Sep 2, 2026
marshallmassengill
left a comment
Contributor
There was a problem hiding this comment.
Two additional blockers to look into:
- ui/src/services/statusService.ts:93 — reads resp.data['businessservice']/['application'], but the DTO lists are @JsonProperty("businessservices")/("applications") (singular = XML element name only); both panels always render "There are no pending alarms." Verified live.
- ui/src/components/Dashboard/panels/RegionalMapPanel.vue:190 — buildFilterClauses() emits node.id==N, which only works on alarm/outage entities; /api/v2/nodes?_s=(node.id==1) 500s ("String cannot be cast to Integer"), so any active category filter blanks the map with no error.
… !smoke The business-service and application status lists read the singular keys `businessservice`/`application`, but the DTO lists serialize their arrays as `businessservices`/`applications` (the singular names are only the XML element names), so both panels always showed all-clear. The service now reads the plural keys and the test mocks the real shape. The shared dashboard filter emitted `node.id==N` for every entity, which is valid where the node is an alias (alarms, outages) but unmapped on /api/v2/nodes, where the id is the root property; the regional map's node query 500'd whenever a category was selected. The clause builder now takes the node-id property, and the map queries nodes with `id` and alarms with `node.id`.
Contributor
Author
|
@marshallmassengill both addressed in
Tests, eslint and |
marshallmassengill
self-requested a review
September 2, 2026 18:10
marshallmassengill
requested changes
Sep 2, 2026
marshallmassengill
left a comment
Contributor
There was a problem hiding this comment.
One more blocker:
- QuickSearchPanel.vue:33,61 — all four forms submit to /opennms/element/nodeList.htm, which no longer exists on develop. No file, no dispatcher-servlet.xml mapping, no @RequestMapping; the only references in the tree are the panel itself. Every Quick Search submit 404s. Current legacy targets: element/node.jsp?node= for node ID, and ui/index.html#/nodes?nodename=… / ?iplike=… / ?monitoredService= for the rest (element/index.jsp:submitNodeSearch). The service select also sends service=; the live param is monitoredService keyed by service name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NMS-20126 (dashboard rework): the panels that reproduce the legacy homepage, and the default layout that arranges them like the front page. Stacks on #8721 (the framework base), so review its top commit.
opennms.newsFeedPanel.show=falsewith no outbound request; the "Try the new Dashboard (beta)" link sits below the Quick Search box.