feat(routing): require explicit inbound DID routes and normalize SIP identities for provider-based number matching
This commit is contained in:
@@ -20,6 +20,9 @@ interface ISipRoute {
|
||||
action: {
|
||||
targets?: string[];
|
||||
ringBrowsers?: boolean;
|
||||
voicemailBox?: string;
|
||||
ivrMenuId?: string;
|
||||
noAnswerTimeout?: number;
|
||||
provider?: string;
|
||||
failoverProviders?: string[];
|
||||
stripPrefix?: string;
|
||||
@@ -40,10 +43,10 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
`,
|
||||
];
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
appState.subscribe((_k, s) => { this.appData = s; });
|
||||
this.loadConfig();
|
||||
async connectedCallback(): Promise<void> {
|
||||
await super.connectedCallback();
|
||||
appState.subscribe((s) => { this.appData = s; });
|
||||
await this.loadConfig();
|
||||
}
|
||||
|
||||
private async loadConfig() {
|
||||
@@ -157,9 +160,15 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
return html`<span style="font-family:'JetBrains Mono',monospace;font-size:.82rem">${parts.join(' ')}</span>`;
|
||||
} else {
|
||||
const parts: string[] = [];
|
||||
if (a.targets?.length) parts.push(`ring: ${a.targets.join(', ')}`);
|
||||
else parts.push('ring: all devices');
|
||||
if (a.ringBrowsers) parts.push('+ browsers');
|
||||
if (a.ivrMenuId) {
|
||||
parts.push(`ivr: ${a.ivrMenuId}`);
|
||||
} else {
|
||||
if (a.targets?.length) parts.push(`ring: ${a.targets.join(', ')}`);
|
||||
else parts.push('ring: all devices');
|
||||
if (a.ringBrowsers) parts.push('+ browsers');
|
||||
}
|
||||
if (a.voicemailBox) parts.push(`vm: ${a.voicemailBox}`);
|
||||
if (a.noAnswerTimeout) parts.push(`timeout: ${a.noAnswerTimeout}s`);
|
||||
return html`<span style="font-family:'JetBrains Mono',monospace;font-size:.82rem">${parts.join(' ')}</span>`;
|
||||
}
|
||||
},
|
||||
@@ -231,6 +240,8 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
const cfg = this.config;
|
||||
const providers = cfg?.providers || [];
|
||||
const devices = cfg?.devices || [];
|
||||
const voiceboxes = cfg?.voiceboxes || [];
|
||||
const ivrMenus = cfg?.ivr?.menus || [];
|
||||
|
||||
const formData: ISipRoute = existing
|
||||
? JSON.parse(JSON.stringify(existing))
|
||||
@@ -284,7 +295,7 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
<dees-input-text
|
||||
.key=${'numberPattern'}
|
||||
.label=${'Number Pattern'}
|
||||
.description=${'Exact, prefix with * (e.g. +49*), or /regex/'}
|
||||
.description=${'Inbound: DID/called number. Outbound: dialed number. Exact, prefix with * (e.g. +49*), or /regex/'}
|
||||
.value=${formData.match.numberPattern || ''}
|
||||
@input=${(e: Event) => { formData.match.numberPattern = (e.target as any).value || undefined; }}
|
||||
></dees-input-text>
|
||||
@@ -328,7 +339,7 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
<dees-input-text
|
||||
.key=${'targets'}
|
||||
.label=${'Ring Devices (comma-separated IDs)'}
|
||||
.description=${'Leave empty to ring all devices'}
|
||||
.description=${'Leave empty to ring all devices for matched inbound numbers'}
|
||||
.value=${(formData.action.targets || []).join(', ')}
|
||||
@input=${(e: Event) => {
|
||||
const v = (e.target as any).value.trim();
|
||||
@@ -342,6 +353,30 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
@newValue=${(e: CustomEvent) => { formData.action.ringBrowsers = e.detail; }}
|
||||
></dees-input-checkbox>
|
||||
|
||||
<dees-input-dropdown
|
||||
.key=${'voicemailBox'} .label=${'Voicemail Box (inbound fallback)'}
|
||||
.selectedOption=${formData.action.voicemailBox
|
||||
? { option: formData.action.voicemailBox, key: formData.action.voicemailBox }
|
||||
: { option: '(none)', key: '' }}
|
||||
.options=${[
|
||||
{ option: '(none)', key: '' },
|
||||
...voiceboxes.map((vb: any) => ({ option: vb.id, key: vb.id })),
|
||||
]}
|
||||
@selectedOption=${(e: CustomEvent) => { formData.action.voicemailBox = e.detail.key || undefined; }}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-dropdown
|
||||
.key=${'ivrMenuId'} .label=${'IVR Menu (inbound)'}
|
||||
.selectedOption=${formData.action.ivrMenuId
|
||||
? { option: formData.action.ivrMenuId, key: formData.action.ivrMenuId }
|
||||
: { option: '(none)', key: '' }}
|
||||
.options=${[
|
||||
{ option: '(none)', key: '' },
|
||||
...ivrMenus.map((menu: any) => ({ option: menu.name || menu.id, key: menu.id })),
|
||||
]}
|
||||
@selectedOption=${(e: CustomEvent) => { formData.action.ivrMenuId = e.detail.key || undefined; }}
|
||||
></dees-input-dropdown>
|
||||
|
||||
<dees-input-text
|
||||
.key=${'stripPrefix'} .label=${'Strip Prefix (outbound)'}
|
||||
.value=${formData.action.stripPrefix || ''}
|
||||
@@ -380,6 +415,9 @@ export class SipproxyViewRoutes extends DeesElement {
|
||||
if (!formData.action.prependPrefix) delete formData.action.prependPrefix;
|
||||
if (!formData.action.targets?.length) delete formData.action.targets;
|
||||
if (!formData.action.ringBrowsers) delete formData.action.ringBrowsers;
|
||||
if (!formData.action.voicemailBox) delete formData.action.voicemailBox;
|
||||
if (!formData.action.ivrMenuId) delete formData.action.ivrMenuId;
|
||||
if (!formData.action.noAnswerTimeout) delete formData.action.noAnswerTimeout;
|
||||
|
||||
const currentRoutes = [...(cfg?.routing?.routes || [])];
|
||||
const idx = currentRoutes.findIndex((r: any) => r.id === formData.id);
|
||||
|
||||
Reference in New Issue
Block a user