NMS-20100: PrimeVue Notifications page with a General configuration tab - #8709
NMS-20100: PrimeVue Notifications page with a General configuration tab#8709joseanesONMS wants to merge 9 commits into
Conversation
Rewrites the Notifications page (notice queries, outstanding/acknowledged notice lists with CSV export and printing, collapsible explanations) as a PrimeVue page backed by REST, and adds a Configure Notifications dialog holding a General tab with the system-wide notification on/off switch. Adds the admin-only /rest/notification-config endpoints for the notifd status and the read side of destination paths (the base API surface for the dependent tab PRs), backed by the same file-backed factories the legacy wizards use — the XML files stay the system of record. Browser delivery of notices for /ui pages ships here as well. The Event Notifications, Destination Paths and Path Outages tabs follow in dependent PRs.
|
@marshallmassengill could you take a review pass when you get a chance? This is the base of the NMS-20100 stack — #8710, #8711 and #8712 each add one tab on top of it. |
…uHeaderIT The menu entry now lands on the Vue Notifications page, so the smoke test waits for its page title instead of the legacy notification/index.jsp card header. NotificationsPageIT still covers the legacy page, which remains reachable by URL.
marshallmassengill
left a comment
There was a problem hiding this comment.
Some stuff that I think is worth adjusting here:
Bugs worth fixing before merge
-
The notices list can hang forever when /whoami fails. AdminNotifications.vue gates noticesStore.load() on authStore.loaded, via whenever(authLoaded, ...). But authStore.getWhoAmI() sets loaded.value = true only inside if (resp), and whoAmIService returns a falsy value on error. So if the whoami call fails for any reason, loaded stays false permanently, the whenever watcher never fires, and the user gets a page that renders "No outstanding notices found." with no spinner and no error — indistinguishable from genuinely having no notices. The legacy JSP had no such dependency. Setting loaded in a finally block (in authStore, which fixes the same latent issue for rolesAreLoaded) is the cleanest fix.
-
One transient failure permanently bricks the config toggle. In ConfigureNotificationsDialog.vue, populated latches to true after the first populate() regardless of whether it succeeded. getNotificationConfigStatus() returns null on error, and the switch is :disabled="store.notifdStatus === null". A single failed GET — a server restart, a 500 — leaves the toggle disabled and the label reading "Notifications are unknown" for the lifetime of the page, and reopening the dialog won't retry because populated is already true. Either latch only on success, or re-populate on every open.
-
Acknowledging the last row of the last page leaves you on a dead page. noticesStore.acknowledge() reloads without rewinding first. When the ack drops totalCount below the current offset, the reload returns zero rows and the component renders EmptyList even though notices exist. applyPreset resets first correctly; acknowledge should clamp it too.
Export path
-
CSV and print silently truncate at 1000 notices. EXPORT_LIMIT = 1000 caps fetchAllForExport, and the print header then states ${rows.length} notices as though that were the whole set. A user exporting 5000 acknowledged notices gets 1000 with no indication anything was dropped. At minimum, compare against store.totalCount and surface a snackbar when the cap bites.
-
CSV formula injection. escapeCell wraps in quotes and doubles embedded quotes, but doesn't neutralise a leading =, +, -, or @. Notice subject and text-message content derives from event data, which on a trap-fed system is externally influenced — so a crafted varbind can land a formula in the exported file that Excel will execute on open. Prefixing such cells with a single quote is the usual mitigation.
|
So I think because of how you split this, we're going to need to do a final pass across everything once it's all in place. I do think this is a better approach though and makes things a bit easier to review. |
A failed whoami no longer hangs loads gated on it, the config dialog retries the status fetch on reopen, acknowledging the last row of the last page rewinds a page, exports surface the 1000-notice cap, and CSV cells are guarded against leading formula characters.
|
All five addressed: the whoami fix went into authStore so it also covers |
There was a problem hiding this comment.
Some more changes here:
- Whoami fallback shows all users' notices under "Your Outstanding Notices." loaded now always flips, but whoAmI.id stays undefined, so effectiveUser → null and browseNotices drops the user filter. Make the yourOutstanding/userSearch presets refuse to run without a user id and surface an error instead of silently widening.
- Dead code in NotificationConfigRestService. Unused javax.ws.rs.DELETE and POST imports, the never-called isBlank, three stray blank lines before the closing brace, and a class javadoc claiming coverage of notifications.xml and notificationCommands.xml that this PR doesn't touch.
- Ack rewind steps back only one page. Clamp first to the last valid offset from totalCount rather than subtracting a single rows.
- sendStatusEvent sends only remoteUser; UpdateNotifdStatusServlet also sends remoteHost and remoteAddr. Match it or note the omission as deliberate.
- OnmsIconButton is driven by bare text/rounded fallthrough attrs instead of the seam's declared variant prop. Still two occurrences in NoticesTable.vue.
- IT hygiene. Shared target/test-work-dir/etc with opennms.home set globally and never restored; the "cold-start ordering regression guard" comment claims more than the test proves; testDestinationPathsReadable still carries a stale comment about sibling tests renaming paths.
- useBrowserNotifications has no test despite running on every /ui page — reconnect loop, array unwrapping, snackbar fallback all unverified.
- Rename AdminNotifications.vue → Notifications (the page isn't admin-gated; only the gear is).
- Declare all four tabs (final order + default) in ConfigureNotificationsDialog.vue with placeholder panels, and replace the populate() Promise.all with per-tab loaders. This removes most of the 7–8 file conflicts each sibling pair currently has, forces the tab-order decision to be deliberate rather than an artifact of merge sequence, and fixes #8710's retry-gap finding as a side effect.
Resolving these should get this into a state for merging at least.
The configuration dialog declares all four tabs with placeholder panels and per-tab loaders that latch only on success: the sibling tab PRs each replace one placeholder instead of restructuring the dialog, the tab order is deliberate, and a failed load retries on the next visit. User-scoped notice queries refuse to run without a user id instead of silently widening to everyone's notices, and the ack rewind clamps to the last valid page. The status event carries remoteHost and remoteAddr like the legacy servlet, dead code is gone from the REST service, the container is Notifications.vue since the page is not admin-gated, OnmsIconButton uses its variant prop, the IT restores opennms.home, and the browser-notification composable gets tests.
|
All ten addressed. The dialog now declares all four tabs with placeholders and per-tab success-latching loaders, so the siblings each replace one panel — their conflicts collapsed to a loader entry and an import. User-scoped queries refuse to run without a user id, the ack clamp derives from totalCount, the status event carries remoteHost/remoteAddr, the container is Notifications.vue, the icon buttons use the variant prop, the IT restores opennms.home, and the composable has a test suite covering the reconnect loop, unwrapping and the snackbar fallback. |
marshallmassengill
left a comment
There was a problem hiding this comment.
A few more but getting close I think... I'm honestly 50/50 on most of these because they are really minor and some of them are going to get addressed with the other PRs:
- Dialog defaults to event-notifications, a placeholder here. Default to general until #8710 lands.
- populate() and getDestinationPaths() now unreferenced, but notificationConfigStore.test.ts:70 still tests them.
- sendStatusEvent reads request.getRemoteHost() outside the try — 500 after the status already persisted.
- 127.0.0.1.tm0.epoch and tmlog0.log committed under opennms-webapp-rest/; all siblings inherit them.
- gear button still text rounded
- setRelativeHomeDirectory not restored
Open the config dialog on the live General tab instead of the Event Notifications placeholder. Send the notifd status event entirely inside the try so a reverse-DNS failure in getRemoteHost can't 500 a change that already persisted. Drop the now-unreferenced destination-path plumbing from the config store and its test. Remove two Atomikos JTA logs committed by mistake and ignore that class of artifact. Clarify that restoring opennms.home fully undoes the IT's setRelativeHomeDirectory calls, and tidy the icon-button variant and a trailing gap.
marshallmassengill
left a comment
There was a problem hiding this comment.
Couple more changes needed:
The menu entry has roles: null, so every user lands on this page, and Acknowledge issues PUT /rest/notifications/{id}, which falls through to the /rest/** catch-all restricted to ROLE_REST,ROLE_ADMIN. Legacy /notification/acknowledge was under /** → ROLE_USER. The fix has precedent ten lines up in the same security file: the alarm block carves out /rest/alarms/*?ack=true for ROLE_USER with !hasRole('ROLE_READONLY').
useRole.ts has no ROLE_READONLY at all, so the button renders for read-only users where list.jsp explicitly hid it
There is also a regression here: fetchAllForExport rebuilds the preset→user mapping itself instead of going through the guarded load(), so it still fails open on a failed whoami.
Acknowledging a notification now has its own /rest/notifications/** security block granting ROLE_USER (not ROLE_READONLY), mirroring the alarms rules, so a normal user can ack as they could under the legacy servlet while notice config editing stays REST/ADMIN-only under /rest/notification-config. The UI adds ROLE_READONLY/ROLE_MOBILE and hides the Acknowledge action from read-only users, and the CSV/print export now routes through the store's whoami guard so a failed whoami can no longer widen a user-scoped export to everyone's notices. NotificationManager and DestinationPathManager route every mutation through a saveWithRollback helper that snapshots the in-memory model, applies the change and saveCurrent(), and restores the snapshot on any failure. This stops a change that leaves the config unmarshallable — removing the last notification/path, or a field-by-field replace that throws mid-way — from leaving memory diverged from the on-disk file until a restart.
marshallmassengill
left a comment
There was a problem hiding this comment.
Fourth-pass review. The saveWithRollback work is the right fix and it resolves the in-memory/on-disk divergence that was blocking #8710 (varbind) and #8711 (deleting the last destination path) by inheritance, so both of those downgrade once the siblings rebase. aFailedReplaceLeavesNoHalfUpdatedEntry covers the hardest case. The ack authz fix, the read-only hide and the export whoami guard all look right, and canAcknowledgeNotifications fails closed on a failed whoami.
One blocker inline: the new PUT rule is broader than its stated intent and lets a normal user mass-unacknowledge everything. The rest are smaller.
Two follow-ups that have no line to attach to here:
- A shared
serverDetail(err, fallback)helper inui/src/services/serviceHelpers.ts. Several server-side guards carry a message that is the only explanation the user gets, and the sibling PRs'deleteandstatuscalls discarderr.response.data. I'll raise it on #8710 and #8711 where it's consumed. addPathandaddNotificationrollback cases in the two new test classes. Both suites cover removal, andNotificationManageralso covers replace, but neither covers an add.
Also worth knowing: all three siblings are still based on e0bcb101c65, one commit behind this.
…oaders Splits the notification PUT security rule: only the single-resource path (/rest/notifications/*) is granted to ROLE_USER, while the collection PUT — which acks or unacks by arbitrary criteria and would let a user mass-unacknowledge the whole system — stays REST/ADMIN. The save-rollback in both config managers now restores inside its own try/catch so a failing restore adds itself as a suppressed exception rather than replacing the original cause, and the shallow-snapshot constraint in DestinationPathManager is documented. getDestinationPaths returns null on failure and getStatus returns a boolean, so the general tab loader reports real success instead of inferring it; a store test pins that contract.
|
All addressed in the latest push:
Noted your point that the authz rule can't be covered by a module IT (the filter chain isn't installed in |
|
With that last one, if it goes green then I think this is ready to merge pending approval from @synqotik. This is going to require the other PRs go in after so it may be that we want to get them all lined up at once or we may want to get this one merged and then rebase the others. Apologies for the churn but this is in a much better state now. |
…ack gap
/rest/notifications/* also matches the trailing-slash form /rest/notifications/,
which JAX-RS routes to the criteria-based collection ack/unack (the {notifId}
regex can't match an empty segment), leaving the mass-unack reachable by a
ROLE_USER. Pin the trailing-slash form to REST/ADMIN ahead of the single-resource
rule; the single-resource and collection rules are unchanged.
|
Good catch on the trailing slash — fixed in Agreed this one isn't module-IT-coverable (the filter chain isn't installed in |
Migrate the notifications page chrome — the Configure dialog (dialog + tabs + toggle), the notices table, and the queries bar — onto the Onms-* seam wrappers so the page passes the no-direct-primevue lint rule. Behaviour and markup are unchanged; iftalabel stays on PrimeVue as it has no wrapper yet.
First slice of the NMS-20100 epic (Notifications admin rework), targeting the epic feature branch
jira/NMS-20100. Carries the PrimeVue Notifications page and a Configure Notifications dialog with only the General tab; the Event Notifications (NMS-20118), Destination Paths (NMS-20119) and Path Outages (NMS-20120) tabs follow in stacked PRs.