Selaa lähdekoodia

fix(frontend): key the node table by the computed row key, not id

The desktop node table used rowKey="id", but transitive sub-nodes (the
read-only rows surfaced from downstream nodes) all carry id 0, so a topology
with two or more transitive rows gave React duplicate keys. antd's rowKey
prop overrides the row object's own computed `key` (`t-${guid}` for
transitive rows, the numeric id otherwise), so the unique key the code
already builds was ignored — causing row-state/DOM mis-association on any
re-render (heartbeat refetch, address-eye toggle). The mobile card path
already keyed by record.key.

Key the table by "key" so transitive rows get their distinct t-${guid}
identity; direct nodes keep key === id, so row selection (filtered to numeric
keys) is unchanged.
MHSanaei 1 päivä sitten
vanhempi
sitoutus
8662c31296

+ 1 - 1
frontend/src/pages/nodes/NodeList.tsx

@@ -650,7 +650,7 @@ export default function NodeList({
           loading={loading}
           scroll={{ x: 'max-content' }}
           size="middle"
-          rowKey="id"
+          rowKey="key"
           rowSelection={dataSource.length > 1 ? {
             selectedRowKeys: selectedIds,
             onChange: (keys) => onSelectionChange(keys.filter((k) => typeof k === 'number') as number[]),

+ 46 - 0
frontend/src/test/node-list-rowkey.test.tsx

@@ -0,0 +1,46 @@
+import { describe, it, expect, vi, afterEach } from 'vitest';
+
+import NodeList from '@/pages/nodes/NodeList';
+import type { NodeRecord } from '@/schemas/node';
+
+import { renderWithProviders } from './test-utils';
+
+const noop = () => {};
+
+function sampleNodes(): NodeRecord[] {
+  return [
+    { id: 1, name: 'parent', guid: 'p1', transitive: false, enable: true, status: 'online' },
+    { id: 0, name: 'child-a', guid: 'ca', parentGuid: 'p1', transitive: true },
+    { id: 0, name: 'child-b', guid: 'cb', parentGuid: 'p1', transitive: true },
+  ];
+}
+
+describe('NodeList desktop table row keys', () => {
+  afterEach(() => vi.restoreAllMocks());
+
+  it('gives transitive sub-node rows distinct keys instead of colliding on id 0', () => {
+    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+    renderWithProviders(
+      <NodeList
+        nodes={sampleNodes()}
+        isMobile={false}
+        selectedIds={[]}
+        onSelectionChange={noop}
+        onAdd={noop}
+        onMtls={noop}
+        onEdit={noop}
+        onDelete={noop}
+        onProbe={noop}
+        onToggleEnable={noop}
+        onUpdateNode={noop}
+        onUpdateSelected={noop}
+      />,
+    );
+
+    const duplicateKeyWarning = errorSpy.mock.calls.some((call) =>
+      call.some((arg) => typeof arg === 'string' && arg.includes('same key')),
+    );
+    expect(duplicateKeyWarning).toBe(false);
+  });
+});