fix(routes): preserve inline target ports when clearing network target references

This commit is contained in:
2026-04-16 22:21:07 +00:00
parent 8bbaf26813
commit d780e02928
6 changed files with 226 additions and 26 deletions

View File

@@ -15,16 +15,70 @@ import {
// TLS dropdown options shared by create and edit dialogs
const tlsModeOptions = [
{ key: 'none', option: '(none — no TLS)' },
{ key: 'passthrough', option: 'Passthrough' },
{ key: 'terminate', option: 'Terminate' },
{ key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt' },
{ key: 'none', option: '(none — plain TCP/HTTP, use for SSH)' },
{ key: 'passthrough', option: 'Passthrough (TLS only)' },
{ key: 'terminate', option: 'Terminate TLS' },
{ key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt TLS' },
];
const tlsCertOptions = [
{ key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
{ key: 'custom', option: 'Custom certificate' },
];
function getDropdownKey(value: any): string {
return typeof value === 'string' ? value : value?.key || '';
}
function parseTargetPort(value: any): number | undefined {
const parsed = typeof value === 'number'
? value
: typeof value === 'string'
? parseInt(value.trim(), 10)
: Number.NaN;
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
return undefined;
}
return parsed;
}
function getRouteTargetInputs(formEl: any) {
const textInputs = Array.from(formEl.querySelectorAll('dees-input-text')) as any[];
return {
hostInput: textInputs.find((input) => input.key === 'targetHost'),
portInput: textInputs.find((input) => input.key === 'targetPort'),
};
}
function setupTargetInputState(formEl: any) {
const updateState = async () => {
const data = await formEl.collectFormData();
const usesNetworkTarget = !!getDropdownKey(data.networkTargetRef);
const { hostInput, portInput } = getRouteTargetInputs(formEl);
const hostDescription = usesNetworkTarget
? 'Controlled by the selected network target'
: 'Used when no network target is selected';
const portDescription = usesNetworkTarget
? 'Controlled by the selected network target'
: 'Used when no network target is selected';
if (hostInput) {
hostInput.disabled = usesNetworkTarget;
hostInput.required = !usesNetworkTarget;
hostInput.description = hostDescription;
}
if (portInput) {
portInput.disabled = usesNetworkTarget;
portInput.required = !usesNetworkTarget;
portInput.description = portDescription;
}
await formEl.updateRequiredStatus?.();
};
formEl.changeSubject.subscribe(() => updateState());
updateState();
}
/**
* Toggle TLS form field visibility based on selected TLS mode and certificate type.
*/
@@ -470,6 +524,16 @@ export class OpsViewRoutes extends DeesElement {
: [];
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
const profileKey = getDropdownKey(formData.sourceProfileRef);
const targetKey = getDropdownKey(formData.networkTargetRef);
const targetPort = parseTargetPort(formData.targetPort)
?? (targetKey ? parseTargetPort(currentTargetPort) ?? ports[0] : undefined);
if (targetPort === undefined) {
alert('Target Port must be a valid port number when no network target is selected.');
return;
}
const updatedRoute: any = {
name: formData.name,
match: {
@@ -480,8 +544,8 @@ export class OpsViewRoutes extends DeesElement {
type: 'forward',
targets: [
{
host: formData.targetHost || 'localhost',
port: parseInt(formData.targetPort, 10) || 443,
host: formData.targetHost || currentTargetHost || 'localhost',
port: targetPort,
},
],
},
@@ -508,15 +572,17 @@ export class OpsViewRoutes extends DeesElement {
}
const metadata: any = {};
const profileRefValue = formData.sourceProfileRef as any;
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
if (profileKey) {
metadata.sourceProfileRef = profileKey;
} else if (merged.metadata?.sourceProfileRef) {
metadata.sourceProfileRef = '';
metadata.sourceProfileName = '';
}
const targetRefValue = formData.networkTargetRef as any;
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
if (targetKey) {
metadata.networkTargetRef = targetKey;
} else if (merged.metadata?.networkTargetRef) {
metadata.networkTargetRef = '';
metadata.networkTargetName = '';
}
await appstate.routeManagementStatePart.dispatchAction(
@@ -537,6 +603,7 @@ export class OpsViewRoutes extends DeesElement {
if (editForm) {
await editForm.updateComplete;
setupTlsVisibility(editForm);
setupTargetInputState(editForm);
}
}
@@ -604,6 +671,16 @@ export class OpsViewRoutes extends DeesElement {
: [];
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
const profileKey = getDropdownKey(formData.sourceProfileRef);
const targetKey = getDropdownKey(formData.networkTargetRef);
const targetPort = parseTargetPort(formData.targetPort)
?? (targetKey ? ports[0] : undefined);
if (targetPort === undefined) {
alert('Target Port must be a valid port number when no network target is selected.');
return;
}
const route: any = {
name: formData.name,
match: {
@@ -615,7 +692,7 @@ export class OpsViewRoutes extends DeesElement {
targets: [
{
host: formData.targetHost || 'localhost',
port: parseInt(formData.targetPort, 10) || 443,
port: targetPort,
},
],
},
@@ -641,13 +718,9 @@ export class OpsViewRoutes extends DeesElement {
// Build metadata if profile/target selected
const metadata: any = {};
const profileRefValue = formData.sourceProfileRef as any;
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
if (profileKey) {
metadata.sourceProfileRef = profileKey;
}
const targetRefValue = formData.networkTargetRef as any;
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
if (targetKey) {
metadata.networkTargetRef = targetKey;
}
@@ -669,6 +742,7 @@ export class OpsViewRoutes extends DeesElement {
if (createForm) {
await createForm.updateComplete;
setupTlsVisibility(createForm);
setupTargetInputState(createForm);
}
}