Kaynağa Gözat

fix(settings): keep the stored port when a port field is cleared (#6121)

* fix(settings): keep the stored port when a port field is cleared

Clearing the panel-port, subscription-port or LDAP-port InputNumber
fired onChange(null), which the handlers coerced to 0; on blur Ant
Design clamped the empty field to min=1 and the next save silently
persisted port 1. For subPort that breaks the generated subscription
links; for webPort it moves the panel itself to port 1 and locks the
admin out until the port is fixed via the x-ui CLI.

Ignore null changes so clearing a port field snaps back to the last
valid value instead of committing a bogus port.

Co-Authored-By: Claude Fable 5 <[email protected]>

* test(settings): pin cleared port fields to the stored value

Clearing the subscription-port field must not reach updateSetting at
all, while typed ports still pass through unchanged. Pins the fix so a
handler refactor cannot silently reintroduce the clamped port 1.

Co-Authored-By: Claude Fable 5 <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
PathGao 10 saat önce
ebeveyn
işleme
579acbc669

+ 2 - 2
frontend/src/pages/settings/GeneralTab.tsx

@@ -170,7 +170,7 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
 
             <SettingListItem paddings="small" title={t('pages.settings.panelPort')} description={t('pages.settings.panelPortDesc')}>
               <InputNumber value={allSetting.webPort} min={1} max={65535} style={{ width: '100%' }}
-                onChange={(v) => updateSetting({ webPort: Number(v) || 0 })} />
+                onChange={(v) => { if (v != null) updateSetting({ webPort: v }); }} />
             </SettingListItem>
 
             <SettingListItem paddings="small" title={t('pages.settings.panelUrlPath')} description={t('pages.settings.panelUrlPathDesc')}>
@@ -308,7 +308,7 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
             </SettingListItem>
             <SettingListItem paddings="small" title={t('pages.settings.ldap.port')}>
               <InputNumber value={allSetting.ldapPort} min={1} max={65535} style={{ width: '100%' }}
-                onChange={(v) => updateSetting({ ldapPort: Number(v) || 0 })} />
+                onChange={(v) => { if (v != null) updateSetting({ ldapPort: v }); }} />
             </SettingListItem>
             <SettingListItem paddings="small" title={t('pages.settings.ldap.useTls')}>
               <Switch checked={allSetting.ldapUseTLS} onChange={(v) => updateSetting({ ldapUseTLS: v })} />

+ 1 - 1
frontend/src/pages/settings/SubscriptionGeneralTab.tsx

@@ -57,7 +57,7 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
             </SettingListItem>
             <SettingListItem paddings="small" title={t('pages.settings.subPort')} description={t('pages.settings.subPortDesc')}>
               <InputNumber value={allSetting.subPort} min={1} max={65535} style={{ width: '100%' }}
-                onChange={(v) => updateSetting({ subPort: Number(v) || 0 })} />
+                onChange={(v) => { if (v != null) updateSetting({ subPort: v }); }} />
             </SettingListItem>
             <SettingListItem paddings="small" title={t('pages.settings.subPath')} description={t('pages.settings.subPathDesc')}>
               <Input

+ 30 - 0
frontend/src/test/subscription-general-tab.test.tsx

@@ -12,6 +12,36 @@ function LocationProbe() {
 }
 
 describe('SubscriptionGeneralTab', () => {
+  it('keeps the stored subscription port when the field is cleared', () => {
+    const updateSetting = vi.fn();
+
+    renderWithProviders(
+      <MemoryRouter initialEntries={['/settings#subscription']}>
+        <SubscriptionGeneralTab allSetting={new AllSetting({ subPort: 2096 })} updateSetting={updateSetting} />
+      </MemoryRouter>,
+    );
+
+    const portInput = screen.getByDisplayValue('2096');
+    fireEvent.change(portInput, { target: { value: '' } });
+    fireEvent.blur(portInput);
+
+    expect(updateSetting).not.toHaveBeenCalled();
+  });
+
+  it('forwards typed subscription ports unchanged', () => {
+    const updateSetting = vi.fn();
+
+    renderWithProviders(
+      <MemoryRouter initialEntries={['/settings#subscription']}>
+        <SubscriptionGeneralTab allSetting={new AllSetting({ subPort: 2096 })} updateSetting={updateSetting} />
+      </MemoryRouter>,
+    );
+
+    fireEvent.change(screen.getByDisplayValue('2096'), { target: { value: '8443' } });
+
+    expect(updateSetting).toHaveBeenCalledWith({ subPort: 8443 });
+  });
+
   it('uses router navigation to open subscription format settings', () => {
     const allSetting = new AllSetting({ subClashEnable: true });