Bläddra i källkod

fix(frontend): preserve routing-rule fields the form does not surface

The rule form rebuilt the rule from a fixed literal of only the fields it
edits, and RoutingTab replaces the rule wholesale on confirm. Fields the
form never exposes — localPort, localIP, process, ruleTag, webhook — are in
the rule schema and can arrive via the advanced JSON editor or Import Rules;
opening such a rule in the form and saving silently dropped them.

Carry over every key of the original rule the form does not manage before
applying the form-derived fields, so an edit only touches what it surfaces.
MHSanaei 1 dag sedan
förälder
incheckning
5f7c2424ef

+ 6 - 0
frontend/src/pages/xray/routing/RuleFormModal.tsx

@@ -126,7 +126,13 @@ export default function RuleFormModal({
       outboundTag: v.outboundTag === '' ? undefined : v.outboundTag,
       balancerTag: v.balancerTag === '' ? undefined : v.balancerTag,
     };
+    const managedKeys = new Set(Object.keys(built));
     const out: Record<string, unknown> = {};
+    if (rule) {
+      for (const [key, value] of Object.entries(rule)) {
+        if (!managedKeys.has(key) && value !== undefined) out[key] = value;
+      }
+    }
     for (const [k, v] of Object.entries(built)) {
       if (v == null) continue;
       if (Array.isArray(v) && v.length === 0) continue;

+ 33 - 0
frontend/src/test/rule-form-preserve-fields.test.tsx

@@ -0,0 +1,33 @@
+import { describe, it, expect, vi } from 'vitest';
+import { fireEvent, screen } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+import RuleFormModal from '@/pages/xray/routing/RuleFormModal';
+
+import { renderWithProviders } from './test-utils';
+
+describe('RuleFormModal edit preserves unsurfaced fields', () => {
+  it('keeps a field the form does not surface (ruleTag) when saving an edit', () => {
+    const onConfirm = vi.fn();
+    const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+
+    renderWithProviders(
+      <QueryClientProvider client={queryClient}>
+        <RuleFormModal
+          open
+          rule={{ type: 'field', outboundTag: 'block', ruleTag: 'my-tag', enabled: true }}
+          inboundTags={[]}
+          outboundTags={['block']}
+          balancerTags={[]}
+          onClose={vi.fn()}
+          onConfirm={onConfirm}
+        />
+      </QueryClientProvider>,
+    );
+
+    fireEvent.click(screen.getByRole('button', { name: 'Save Changes' }));
+
+    expect(onConfirm).toHaveBeenCalledTimes(1);
+    expect(onConfirm.mock.calls[0][0]).toMatchObject({ ruleTag: 'my-tag' });
+  });
+});