fix(notifications): neutralize satellite-local node names in alert bodies (#1640)

* fix(notifications): neutralize satellite-local node names in alert bodies

Fleet-aggregated alerts embedded each instance seed name (often Local)
while the hub badge already named the remote. Drop identity prefixes and
use type-aware local wording so attribution stays on the badge.

* fix(docs): correct image-update default check cadence

Operator docs still said six-hour polling; the seeded default is two
hours in interval mode, and the cadence is configurable or cron-based.

* test(notifications): assert hub stamps roster name on neutral remote bodies

Cover the fan-in path that attaches hub roster identity while leaving the
satellite message body unchanged.
This commit is contained in:
Anso
2026-07-16 15:51:52 -04:00
committed by GitHub
parent b91025dc8b
commit d8e4ede94f
12 changed files with 288 additions and 48 deletions
@@ -193,6 +193,27 @@ describe('useNotifications', () => {
await waitFor(() => expect(fetchForNode).toHaveBeenCalledWith('/notifications', 2));
});
it('stamps roster nodeName on remote REST notifications without rewriting the body', async () => {
const remote = makeRemoteNode('online', { id: 2, name: 'sencho-sat-qa' });
const neutralBody =
'Scheduled task "qa-missing-container" (restart) failed: Container "web" not found on this node. It may have been renamed or removed.';
(apiFetch as ReturnType<typeof vi.fn>).mockResolvedValue({ ok: true, json: async () => [] });
(fetchForNode as ReturnType<typeof vi.fn>).mockResolvedValue({
ok: true,
json: async () => [{ id: 9, level: 'error', message: neutralBody, timestamp: 2000, is_read: 0 }],
});
const { result } = renderHook(() =>
useNotifications({ nodes: [localNode, remote], onStateInvalidate: vi.fn(), onImageUpdatesChange: vi.fn() }),
);
await waitFor(() => expect(result.current.notifications).toHaveLength(1));
expect(result.current.notifications[0].message).toBe(neutralBody);
expect(result.current.notifications[0].nodeId).toBe(2);
expect(result.current.notifications[0].nodeName).toBe('sencho-sat-qa');
expect(result.current.notifications[0].message).not.toMatch(/\[Node:|Local/);
});
it('subscribes to the online node and skips the offline one in a mixed fleet', async () => {
renderHook(() =>
useNotifications({