import { DeesElement, customElement, html, css, cssManager, state, type TemplateResult } from '../plugins.js'; import { type IStatsTile } from '@design.estate/dees-catalog'; import { appState, type IAppState } from '../state/appstate.js'; import { viewHostCss } from './shared/index.js'; @customElement('sipproxy-view-overview') export class SipproxyViewOverview extends DeesElement { @state() accessor appData: IAppState = appState.getState(); public static styles = [ cssManager.defaultStyles, viewHostCss, css` :host { display: block; padding: 24px; } .section-heading { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: #64748b; margin: 32px 0 12px; } .section-heading:first-of-type { margin-top: 24px; } `, ]; connectedCallback() { super.connectedCallback(); this.rxSubscriptions.push({ unsubscribe: appState.subscribe((s) => { this.appData = s; }), } as any); } private fmtUptime(sec: number): string { const d = Math.floor(sec / 86400); const h = Math.floor((sec % 86400) / 3600); const m = Math.floor((sec % 3600) / 60); const s = sec % 60; if (d > 0) return `${d}d ${h}h ${m}m`; return `${h}h ${String(m).padStart(2, '0')}m ${String(s).padStart(2, '0')}s`; } public render(): TemplateResult { const { appData } = this; const activeCalls = appData.calls?.filter((c) => c.state !== 'terminated') || []; const activeCount = activeCalls.length; const registeredProviders = appData.providers?.filter((p) => p.registered).length || 0; const connectedDevices = appData.devices?.filter((d) => d.connected).length || 0; const inboundCalls = activeCalls.filter((c) => c.direction === 'inbound').length; const outboundCalls = activeCalls.filter((c) => c.direction === 'outbound').length; const webrtcSessions = activeCalls.reduce( (sum, c) => sum + c.legs.filter((l) => l.type === 'webrtc').length, 0, ); const totalRtpPackets = activeCalls.reduce( (sum, c) => sum + c.legs.reduce((lsum, l) => lsum + l.pktSent + l.pktReceived, 0), 0, ); const tiles: IStatsTile[] = [ { id: 'active-calls', title: 'Active Calls', value: activeCount, type: 'number', icon: 'lucide:phone', color: 'hsl(142.1 76.2% 36.3%)', description: activeCount === 1 ? '1 call in progress' : `${activeCount} calls in progress`, }, { id: 'providers', title: 'Registered Providers', value: registeredProviders, type: 'number', icon: 'lucide:server', color: 'hsl(217.2 91.2% 59.8%)', description: `${appData.providers?.length || 0} configured`, }, { id: 'devices', title: 'Connected Devices', value: connectedDevices, type: 'number', icon: 'lucide:wifi', color: 'hsl(270 70% 60%)', description: `${appData.devices?.length || 0} total`, }, { id: 'uptime', title: 'Uptime', value: this.fmtUptime(appData.uptime), type: 'text', icon: 'lucide:clock', description: 'Since last restart', }, { id: 'inbound', title: 'Inbound Calls', value: inboundCalls, type: 'number', icon: 'lucide:phone-incoming', description: 'Currently active', }, { id: 'outbound', title: 'Outbound Calls', value: outboundCalls, type: 'number', icon: 'lucide:phone-outgoing', description: 'Currently active', }, { id: 'webrtc', title: 'WebRTC Sessions', value: webrtcSessions, type: 'number', icon: 'lucide:globe', color: 'hsl(166 72% 40%)', description: 'Browser connections', }, { id: 'rtp-packets', title: 'Total RTP Packets', value: totalRtpPackets, type: 'number', icon: 'lucide:radio', description: 'Sent + received across legs', }, ]; const allDevices = appData.devices || []; const onlineCount = allDevices.filter((d) => d.connected).length; return html`
Devices
{ const on = val === true; return html` ${on ? 'Online' : 'Offline'} `; }, }, { key: 'displayName', header: 'Device', sortable: true, }, { key: 'type', header: 'Type', value: (row: any) => (row.isBrowser ? 'Browser' : 'SIP Device'), renderer: (val: string, row: any) => { const isBrowser = row.isBrowser; const bg = isBrowser ? '#065f46' : '#1e3a5f'; const fg = isBrowser ? '#34d399' : '#38bdf8'; return html`${val}`; }, }, { key: 'contact', header: 'Contact', renderer: (_val: any, row: any) => { const c = row.contact; const text = c ? (c.port ? `${c.address}:${c.port}` : c.address) : '--'; return html`${text}`; }, }, { key: 'aor', header: 'AOR', renderer: (val: any) => html`${val || '--'}`, }, ]} > `; } }