Full-featured SIP router with multi-provider trunking, browser softphone via WebRTC, real-time Opus/G.722/PCM transcoding in Rust, RNNoise ML noise suppression, Kokoro neural TTS announcements, and a Lit-based web dashboard with live call monitoring and REST API.
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { DeesElement, customElement, html, css, cssManager, state, type TemplateResult } from '../plugins.js';
|
|
import { deesCatalog } from '../plugins.js';
|
|
import { NotificationManager } from '../state/notification-manager.js';
|
|
import { appRouter } from '../router.js';
|
|
import { SipproxyViewOverview } from './sipproxy-view-overview.js';
|
|
import { SipproxyViewCalls } from './sipproxy-view-calls.js';
|
|
import { SipproxyViewPhone } from './sipproxy-view-phone.js';
|
|
import { SipproxyViewContacts } from './sipproxy-view-contacts.js';
|
|
import { SipproxyViewProviders } from './sipproxy-view-providers.js';
|
|
import { SipproxyViewLog } from './sipproxy-view-log.js';
|
|
|
|
const VIEW_TABS = [
|
|
{ name: 'Overview', iconName: 'lucide:layoutDashboard', element: SipproxyViewOverview },
|
|
{ name: 'Calls', iconName: 'lucide:phone', element: SipproxyViewCalls },
|
|
{ name: 'Phone', iconName: 'lucide:headset', element: SipproxyViewPhone },
|
|
{ name: 'Contacts', iconName: 'lucide:contactRound', element: SipproxyViewContacts },
|
|
{ name: 'Providers', iconName: 'lucide:server', element: SipproxyViewProviders },
|
|
{ name: 'Log', iconName: 'lucide:scrollText', element: SipproxyViewLog },
|
|
];
|
|
|
|
// Map slug -> tab for routing.
|
|
const SLUG_TO_TAB = new Map(VIEW_TABS.map((t) => [t.name.toLowerCase(), t]));
|
|
|
|
@customElement('sipproxy-app')
|
|
export class SipproxyApp extends DeesElement {
|
|
private notificationManager = new NotificationManager();
|
|
private appdash: InstanceType<typeof deesCatalog.DeesSimpleAppDash> | null = null;
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
css`
|
|
:host { display: block; height: 100%; }
|
|
dees-simple-appdash { height: 100%; }
|
|
`,
|
|
];
|
|
|
|
private suppressViewSelectEvent = false;
|
|
|
|
async firstUpdated() {
|
|
this.appdash = this.shadowRoot?.querySelector('dees-simple-appdash') as InstanceType<typeof deesCatalog.DeesSimpleAppDash>;
|
|
if (this.appdash) {
|
|
this.notificationManager.init(this.appdash);
|
|
|
|
// Listen for user tab selections — sync URL.
|
|
this.appdash.addEventListener('view-select', ((e: CustomEvent) => {
|
|
if (this.suppressViewSelectEvent) return;
|
|
const viewName: string = e.detail?.view?.name || e.detail?.name || '';
|
|
const slug = viewName.toLowerCase();
|
|
if (slug && slug !== appRouter.getCurrentView()) {
|
|
appRouter.navigateTo(slug as any, true);
|
|
}
|
|
}) as EventListener);
|
|
|
|
// Wire up router -> appdash (for browser back/forward).
|
|
appRouter.setNavigateHandler((view) => {
|
|
const tab = SLUG_TO_TAB.get(view);
|
|
if (tab && this.appdash) {
|
|
this.suppressViewSelectEvent = true;
|
|
this.appdash.loadView(tab);
|
|
this.suppressViewSelectEvent = false;
|
|
}
|
|
});
|
|
|
|
// Deep link: if URL isn't "overview", navigate to the right tab.
|
|
const initial = appRouter.getCurrentView();
|
|
if (initial !== 'overview') {
|
|
const tab = SLUG_TO_TAB.get(initial);
|
|
if (tab) {
|
|
this.suppressViewSelectEvent = true;
|
|
this.appdash.loadView(tab);
|
|
this.suppressViewSelectEvent = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
super.disconnectedCallback();
|
|
this.notificationManager.destroy();
|
|
}
|
|
|
|
public render(): TemplateResult {
|
|
return html`
|
|
<dees-simple-appdash
|
|
.name=${'SipRouter'}
|
|
.viewTabs=${VIEW_TABS}
|
|
></dees-simple-appdash>
|
|
`;
|
|
}
|
|
}
|