fix(proxy-engine): improve inbound SIP routing diagnostics and enrich leg media state reporting
This commit is contained in:
@@ -18,6 +18,12 @@ interface IVoicemailMessage {
|
||||
heard: boolean;
|
||||
}
|
||||
|
||||
interface IVoiceboxRow {
|
||||
id: string;
|
||||
unheardCount: number;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -61,19 +67,6 @@ export class SipproxyViewVoicemail extends DeesElement {
|
||||
.view-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.box-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.box-selector label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.audio-player {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -135,10 +128,11 @@ export class SipproxyViewVoicemail extends DeesElement {
|
||||
const cfg = await appState.apiGetConfig();
|
||||
const boxes: { id: string }[] = cfg.voiceboxes || [];
|
||||
this.voiceboxIds = boxes.map((b) => b.id);
|
||||
if (this.voiceboxIds.length > 0 && !this.selectedBoxId) {
|
||||
this.selectedBoxId = this.voiceboxIds[0];
|
||||
await this.loadMessages();
|
||||
}
|
||||
const nextSelectedBoxId = this.voiceboxIds.includes(this.selectedBoxId)
|
||||
? this.selectedBoxId
|
||||
: (this.voiceboxIds[0] || '');
|
||||
this.selectedBoxId = nextSelectedBoxId;
|
||||
await this.loadMessages();
|
||||
} catch {
|
||||
// Config unavailable.
|
||||
}
|
||||
@@ -161,11 +155,22 @@ export class SipproxyViewVoicemail extends DeesElement {
|
||||
}
|
||||
|
||||
private async selectBox(boxId: string) {
|
||||
if (boxId === this.selectedBoxId) {
|
||||
return;
|
||||
}
|
||||
this.selectedBoxId = boxId;
|
||||
this.stopAudio();
|
||||
await this.loadMessages();
|
||||
}
|
||||
|
||||
private getVoiceboxRows(): IVoiceboxRow[] {
|
||||
return this.voiceboxIds.map((id) => ({
|
||||
id,
|
||||
unheardCount: this.appData.voicemailCounts[id] || 0,
|
||||
selected: id === this.selectedBoxId,
|
||||
}));
|
||||
}
|
||||
|
||||
// ---- audio playback ------------------------------------------------------
|
||||
|
||||
private playMessage(msg: IVoicemailMessage) {
|
||||
@@ -341,6 +346,43 @@ export class SipproxyViewVoicemail extends DeesElement {
|
||||
];
|
||||
}
|
||||
|
||||
private getVoiceboxColumns() {
|
||||
return [
|
||||
{
|
||||
key: 'id',
|
||||
header: 'Voicebox',
|
||||
sortable: true,
|
||||
renderer: (val: string, row: IVoiceboxRow) => html`
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<span style="font-family:'JetBrains Mono',monospace;font-size:.85rem;">${val}</span>
|
||||
${row.selected ? html`
|
||||
<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:.7rem;font-weight:600;text-transform:uppercase;background:#1e3a5f;color:#60a5fa">Viewing</span>
|
||||
` : ''}
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
{
|
||||
key: 'unheardCount',
|
||||
header: 'Unheard',
|
||||
sortable: true,
|
||||
renderer: (val: number) => {
|
||||
const hasUnheard = val > 0;
|
||||
return html`
|
||||
<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:.75rem;font-weight:600;background:${hasUnheard ? '#422006' : '#1f2937'};color:${hasUnheard ? '#f59e0b' : '#94a3b8'}">${val}</span>
|
||||
`;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'selected',
|
||||
header: 'Status',
|
||||
value: (row: IVoiceboxRow) => (row.selected ? 'Open' : 'Available'),
|
||||
renderer: (val: string, row: IVoiceboxRow) => html`
|
||||
<span style="color:${row.selected ? '#60a5fa' : '#94a3b8'};font-size:.8rem;">${val}</span>
|
||||
`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ---- table actions -------------------------------------------------------
|
||||
|
||||
private getDataActions() {
|
||||
@@ -390,21 +432,43 @@ export class SipproxyViewVoicemail extends DeesElement {
|
||||
];
|
||||
}
|
||||
|
||||
private getVoiceboxActions() {
|
||||
return [
|
||||
{
|
||||
name: 'View Messages',
|
||||
iconName: 'lucide:folder-open',
|
||||
type: ['inRow'] as any,
|
||||
actionFunc: async ({ item }: { item: IVoiceboxRow }) => {
|
||||
await this.selectBox(item.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Refresh Boxes',
|
||||
iconName: 'lucide:refreshCw',
|
||||
type: ['header'] as any,
|
||||
actionFunc: async () => {
|
||||
await this.loadVoiceboxes();
|
||||
deesCatalog.DeesToast.success('Voiceboxes refreshed');
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ---- render --------------------------------------------------------------
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${this.voiceboxIds.length > 1 ? html`
|
||||
<div class="box-selector">
|
||||
<label>Voicebox</label>
|
||||
<dees-input-dropdown
|
||||
.key=${'voicebox'}
|
||||
.selectedOption=${{ option: this.selectedBoxId, key: this.selectedBoxId }}
|
||||
.options=${this.voiceboxIds.map((id) => ({ option: id, key: id }))}
|
||||
@selectedOption=${(e: CustomEvent) => { this.selectBox(e.detail.key); }}
|
||||
></dees-input-dropdown>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="view-section">
|
||||
<dees-table
|
||||
heading1="Voiceboxes"
|
||||
heading2="${this.voiceboxIds.length} configured"
|
||||
dataName="voiceboxes"
|
||||
.data=${this.getVoiceboxRows()}
|
||||
.rowKey=${'id'}
|
||||
.columns=${this.getVoiceboxColumns()}
|
||||
.dataActions=${this.getVoiceboxActions()}
|
||||
></dees-table>
|
||||
</div>
|
||||
|
||||
<div class="view-section">
|
||||
<dees-statsgrid
|
||||
|
||||
Reference in New Issue
Block a user