feat(routes): add TLS configuration controls for route create and edit flows
This commit is contained in:
@@ -13,6 +13,40 @@ import {
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
// 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' },
|
||||
];
|
||||
const tlsCertOptions = [
|
||||
{ key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
|
||||
{ key: 'custom', option: 'Custom certificate' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Toggle TLS form field visibility based on selected TLS mode and certificate type.
|
||||
*/
|
||||
function setupTlsVisibility(formEl: any) {
|
||||
const updateVisibility = async () => {
|
||||
const data = await formEl.collectFormData();
|
||||
const contentEl = formEl.closest('.content') || formEl.parentElement;
|
||||
if (!contentEl) return;
|
||||
const tlsModeValue = data.tlsMode;
|
||||
const modeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
||||
const needsCert = modeKey === 'terminate' || modeKey === 'terminate-and-reencrypt';
|
||||
const certGroup = contentEl.querySelector('.tlsCertificateGroup') as HTMLElement;
|
||||
if (certGroup) certGroup.style.display = needsCert ? 'flex' : 'none';
|
||||
const tlsCertValue = data.tlsCertificate;
|
||||
const certKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
||||
const customGroup = contentEl.querySelector('.tlsCustomCertGroup') as HTMLElement;
|
||||
if (customGroup) customGroup.style.display = (needsCert && certKey === 'custom') ? 'flex' : 'none';
|
||||
};
|
||||
formEl.changeSubject.subscribe(() => updateVisibility());
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
@customElement('ops-view-routes')
|
||||
export class OpsViewRoutes extends DeesElement {
|
||||
@state() accessor routeState: appstate.IRouteManagementState = {
|
||||
@@ -423,7 +457,18 @@ export class OpsViewRoutes extends DeesElement {
|
||||
: '';
|
||||
const currentTargetPort = firstTarget?.port != null ? String(firstTarget.port) : '';
|
||||
|
||||
await DeesModal.createAndShow({
|
||||
// Compute current TLS state for pre-population
|
||||
const currentTls = (route.action as any).tls;
|
||||
const currentTlsMode = currentTls?.mode || 'none';
|
||||
const currentTlsCert = currentTls
|
||||
? (currentTls.certificate === 'auto' || !currentTls.certificate ? 'auto' : 'custom')
|
||||
: 'auto';
|
||||
const currentCustomKey = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.key : '';
|
||||
const currentCustomCert = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.cert : '';
|
||||
const needsCert = currentTlsMode === 'terminate' || currentTlsMode === 'terminate-and-reencrypt';
|
||||
const isCustom = currentTlsCert === 'custom';
|
||||
|
||||
const editModal = await DeesModal.createAndShow({
|
||||
heading: `Edit Route: ${route.name}`,
|
||||
content: html`
|
||||
<dees-form>
|
||||
@@ -435,6 +480,14 @@ export class OpsViewRoutes extends DeesElement {
|
||||
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
|
||||
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
|
||||
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'} .value=${currentTargetPort}></dees-input-text>
|
||||
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions.find((o) => o.key === currentTlsMode) || tlsModeOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCertificateGroup" style="display: ${needsCert ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
||||
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions.find((o) => o.key === currentTlsCert) || tlsCertOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCustomCertGroup" style="display: ${needsCert && isCustom ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
||||
<dees-input-text .key=${'tlsCertKey'} .label=${'Private Key (PEM)'} .value=${currentCustomKey}></dees-input-text>
|
||||
<dees-input-text .key=${'tlsCertCert'} .label=${'Certificate (PEM)'} .value=${currentCustomCert}></dees-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
@@ -476,6 +529,25 @@ export class OpsViewRoutes extends DeesElement {
|
||||
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
||||
};
|
||||
|
||||
// Build TLS config from form
|
||||
const tlsModeValue = formData.tlsMode as any;
|
||||
const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
||||
if (tlsModeKey && tlsModeKey !== 'none') {
|
||||
const tls: any = { mode: tlsModeKey };
|
||||
if (tlsModeKey !== 'passthrough') {
|
||||
const tlsCertValue = formData.tlsCertificate as any;
|
||||
const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
||||
if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
|
||||
tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
|
||||
} else {
|
||||
tls.certificate = 'auto';
|
||||
}
|
||||
}
|
||||
updatedRoute.action.tls = tls;
|
||||
} else {
|
||||
updatedRoute.action.tls = null; // explicit removal
|
||||
}
|
||||
|
||||
const metadata: any = {};
|
||||
const profileRefValue = formData.securityProfileRef as any;
|
||||
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
|
||||
@@ -501,6 +573,12 @@ export class OpsViewRoutes extends DeesElement {
|
||||
},
|
||||
],
|
||||
});
|
||||
// Setup conditional TLS field visibility after modal renders
|
||||
const editForm = editModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form') as any;
|
||||
if (editForm) {
|
||||
await editForm.updateComplete;
|
||||
setupTlsVisibility(editForm);
|
||||
}
|
||||
}
|
||||
|
||||
private async showCreateRouteDialog() {
|
||||
@@ -524,7 +602,7 @@ export class OpsViewRoutes extends DeesElement {
|
||||
})),
|
||||
];
|
||||
|
||||
await DeesModal.createAndShow({
|
||||
const createModal = await DeesModal.createAndShow({
|
||||
heading: 'Add Programmatic Route',
|
||||
content: html`
|
||||
<dees-form>
|
||||
@@ -536,6 +614,14 @@ export class OpsViewRoutes extends DeesElement {
|
||||
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
|
||||
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${'localhost'}></dees-input-text>
|
||||
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'}></dees-input-text>
|
||||
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCertificateGroup" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions[0]}></dees-input-dropdown>
|
||||
<div class="tlsCustomCertGroup" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<dees-input-text .key=${'tlsCertKey'} .label=${'Private Key (PEM)'}></dees-input-text>
|
||||
<dees-input-text .key=${'tlsCertCert'} .label=${'Certificate (PEM)'}></dees-input-text>
|
||||
</div>
|
||||
</div>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
@@ -577,6 +663,23 @@ export class OpsViewRoutes extends DeesElement {
|
||||
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
||||
};
|
||||
|
||||
// Build TLS config from form
|
||||
const tlsModeValue = formData.tlsMode as any;
|
||||
const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
||||
if (tlsModeKey && tlsModeKey !== 'none') {
|
||||
const tls: any = { mode: tlsModeKey };
|
||||
if (tlsModeKey !== 'passthrough') {
|
||||
const tlsCertValue = formData.tlsCertificate as any;
|
||||
const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
||||
if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
|
||||
tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
|
||||
} else {
|
||||
tls.certificate = 'auto';
|
||||
}
|
||||
}
|
||||
route.action.tls = tls;
|
||||
}
|
||||
|
||||
// Build metadata if profile/target selected
|
||||
const metadata: any = {};
|
||||
const profileRefValue = formData.securityProfileRef as any;
|
||||
@@ -602,6 +705,12 @@ export class OpsViewRoutes extends DeesElement {
|
||||
},
|
||||
],
|
||||
});
|
||||
// Setup conditional TLS field visibility after modal renders
|
||||
const createForm = createModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form') as any;
|
||||
if (createForm) {
|
||||
await createForm.updateComplete;
|
||||
setupTlsVisibility(createForm);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshData() {
|
||||
|
||||
Reference in New Issue
Block a user