|
@@ -138,6 +138,7 @@ interface InboundFormModalProps {
|
|
|
dbInbound: DBInbound | null;
|
|
dbInbound: DBInbound | null;
|
|
|
dbInbounds: DBInbound[];
|
|
dbInbounds: DBInbound[];
|
|
|
availableNodes?: NodeRecord[];
|
|
availableNodes?: NodeRecord[];
|
|
|
|
|
+ availableNodesFetched?: boolean;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function buildAddModeValues(): InboundFormValues {
|
|
function buildAddModeValues(): InboundFormValues {
|
|
@@ -167,6 +168,7 @@ export default function InboundFormModal({
|
|
|
dbInbound,
|
|
dbInbound,
|
|
|
dbInbounds,
|
|
dbInbounds,
|
|
|
availableNodes,
|
|
availableNodes,
|
|
|
|
|
+ availableNodesFetched = true,
|
|
|
}: InboundFormModalProps) {
|
|
}: InboundFormModalProps) {
|
|
|
const { t } = useTranslation();
|
|
const { t } = useTranslation();
|
|
|
const [messageApi, messageContextHolder] = message.useMessage();
|
|
const [messageApi, messageContextHolder] = message.useMessage();
|
|
@@ -373,14 +375,22 @@ export default function InboundFormModal({
|
|
|
// offered (no node, or a protocol that can't deploy to one) fall back to
|
|
// offered (no node, or a protocol that can't deploy to one) fall back to
|
|
|
// `listen`, which yields the same link for a local inbound. Mirrors how the
|
|
// `listen`, which yields the same link for a local inbound. Mirrors how the
|
|
|
// protocol reset drops a nodeId that no longer applies.
|
|
// protocol reset drops a nodeId that no longer applies.
|
|
|
|
|
+ // Only downgrade once the inputs this decision depends on are settled, so a
|
|
|
|
|
+ // persisted `node` strategy is never clobbered by transient mount state (#5375):
|
|
|
|
|
+ // - `availableNodesFetched`: an empty `availableNodes` during the async
|
|
|
|
|
+ // /nodes/list fetch is a placeholder, not "no nodes".
|
|
|
|
|
+ // - `protocol`: `Form.useWatch('protocol')` is briefly empty on the first
|
|
|
|
|
+ // edit render before initialValues apply, which would momentarily make the
|
|
|
|
|
+ // node option look unavailable.
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!open) return;
|
|
if (!open) return;
|
|
|
|
|
+ if (!availableNodesFetched || !protocol) return;
|
|
|
const current = form.getFieldValue('shareAddrStrategy') as InboundFormValues['shareAddrStrategy'] | undefined;
|
|
const current = form.getFieldValue('shareAddrStrategy') as InboundFormValues['shareAddrStrategy'] | undefined;
|
|
|
if (!nodeShareOptionAvailable && (current ?? 'node') === 'node') {
|
|
if (!nodeShareOptionAvailable && (current ?? 'node') === 'node') {
|
|
|
form.setFieldValue('shareAddrStrategy', 'listen');
|
|
form.setFieldValue('shareAddrStrategy', 'listen');
|
|
|
}
|
|
}
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
- }, [open, nodeShareOptionAvailable, shareAddrStrategy]);
|
|
|
|
|
|
|
+ }, [open, availableNodesFetched, protocol, nodeShareOptionAvailable, shareAddrStrategy]);
|
|
|
|
|
|
|
|
// Why: protocol picker reset cascades through the form — clearing the
|
|
// Why: protocol picker reset cascades through the form — clearing the
|
|
|
// settings DU branch and dropping a nodeId that no longer applies. The
|
|
// settings DU branch and dropping a nodeId that no longer applies. The
|