feat(fax): add fax routing, job tracking, inbox management, and T.38/UDPTL media support
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: 'siprouter',
|
||||
version: '1.25.2',
|
||||
version: '1.26.0',
|
||||
description: 'undefined'
|
||||
}
|
||||
|
||||
@@ -175,36 +175,240 @@ export class SipproxyViewCalls extends DeesElement {
|
||||
|
||||
.call-body {
|
||||
padding: 12px 16px 16px;
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.legs-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 12px;
|
||||
.call-overview {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.6fr) minmax(240px, 0.9fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.legs-table th {
|
||||
text-align: left;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
font-size: 0.65rem;
|
||||
.call-route-card,
|
||||
.call-facts-card,
|
||||
.legs-section {
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(51, 65, 85, 0.75);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(15, 23, 42, 0.92) 0%, rgba(8, 15, 31, 0.88) 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(148, 163, 184, 0.08);
|
||||
}
|
||||
|
||||
.call-route-card,
|
||||
.call-facts-card {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.section-kicker {
|
||||
font-size: 0.62rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid var(--dees-color-border-default, #334155);
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.legs-table td {
|
||||
padding: 8px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
border-bottom: 1px solid var(--dees-color-border-subtle, rgba(51, 65, 85, 0.5));
|
||||
vertical-align: middle;
|
||||
.route-line {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.legs-table tr:last-child td {
|
||||
.route-party {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
border: 1px solid rgba(71, 85, 105, 0.45);
|
||||
}
|
||||
|
||||
.route-party.align-end {
|
||||
text-align: right;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.route-party-label {
|
||||
font-size: 0.64rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.route-party-value {
|
||||
min-width: 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #e2e8f0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.route-arrow {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
color: #93c5fd;
|
||||
background: rgba(30, 41, 59, 0.8);
|
||||
border: 1px solid rgba(59, 130, 246, 0.35);
|
||||
}
|
||||
|
||||
.call-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.subtle-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 9px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
color: #cbd5e1;
|
||||
background: rgba(30, 41, 59, 0.9);
|
||||
border: 1px solid rgba(71, 85, 105, 0.45);
|
||||
}
|
||||
|
||||
.call-facts-card {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fact-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 7px 0;
|
||||
border-bottom: 1px solid rgba(51, 65, 85, 0.55);
|
||||
}
|
||||
|
||||
.fact-row:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.fact-label {
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.fact-value {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.76rem;
|
||||
text-align: right;
|
||||
color: #e2e8f0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.legs-section {
|
||||
padding: 14px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.legs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.legs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.leg-card {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
border: 1px solid rgba(71, 85, 105, 0.4);
|
||||
}
|
||||
|
||||
.leg-card-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.leg-card-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.leg-card-id {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.64rem;
|
||||
color: #64748b;
|
||||
word-break: break-all;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.leg-facts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 12px;
|
||||
}
|
||||
|
||||
.leg-fact {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.leg-fact-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.leg-fact-label {
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.leg-fact-value {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.76rem;
|
||||
color: #e2e8f0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.leg-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.no-legs {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px dashed rgba(71, 85, 105, 0.55);
|
||||
color: #64748b;
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
@@ -247,6 +451,34 @@ export class SipproxyViewCalls extends DeesElement {
|
||||
.empty-state-icon { font-size: 2.5rem; margin-bottom: 12px; opacity: 0.4; }
|
||||
.empty-state-text { font-size: 0.9rem; font-weight: 500; }
|
||||
.empty-state-sub { font-size: 0.75rem; margin-top: 4px; }
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.call-overview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.route-line {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.route-arrow {
|
||||
justify-self: center;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.route-party.align-end {
|
||||
text-align: left;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.leg-card-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.leg-card-id {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -550,61 +782,110 @@ export class SipproxyViewCalls extends DeesElement {
|
||||
</div>
|
||||
<div class="call-id">${call.id}</div>
|
||||
<div class="call-body">
|
||||
${call.legs.length
|
||||
? html`
|
||||
<table class="legs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>State</th>
|
||||
<th>Remote</th>
|
||||
<th>Port</th>
|
||||
<th>Codec</th>
|
||||
<th>Pkts In</th>
|
||||
<th>Pkts Out</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div class="call-overview">
|
||||
<div class="call-route-card">
|
||||
<div class="section-kicker">Call Route</div>
|
||||
<div class="route-line">
|
||||
<div class="route-party">
|
||||
<div class="route-party-label">From</div>
|
||||
<div class="route-party-value">${call.callerNumber || 'Unknown caller'}</div>
|
||||
</div>
|
||||
<div class="route-arrow">${directionIcon(call.direction)}</div>
|
||||
<div class="route-party align-end">
|
||||
<div class="route-party-label">To</div>
|
||||
<div class="route-party-value">${call.calleeNumber || 'System'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="call-tags">
|
||||
<span class="subtle-badge">${call.legs.length} ${call.legs.length === 1 ? 'leg' : 'legs'}</span>
|
||||
<span class="subtle-badge">${call.providerUsed || 'system handled'}</span>
|
||||
<span class="subtle-badge">started ${fmtTime(call.startedAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="call-facts-card">
|
||||
<div class="section-kicker">Session</div>
|
||||
<div class="fact-row">
|
||||
<span class="fact-label">State</span>
|
||||
<span class="fact-value">${STATE_LABELS[call.state] || call.state}</span>
|
||||
</div>
|
||||
<div class="fact-row">
|
||||
<span class="fact-label">Direction</span>
|
||||
<span class="fact-value">${call.direction}</span>
|
||||
</div>
|
||||
<div class="fact-row">
|
||||
<span class="fact-label">Duration</span>
|
||||
<span class="fact-value">${fmtDuration(call.duration)}</span>
|
||||
</div>
|
||||
<div class="fact-row">
|
||||
<span class="fact-label">Provider</span>
|
||||
<span class="fact-value">${call.providerUsed || '--'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="legs-section">
|
||||
<div class="legs-header">
|
||||
<div class="section-kicker">Active Legs</div>
|
||||
<span class="subtle-badge">${call.legs.length}</span>
|
||||
</div>
|
||||
|
||||
${call.legs.length
|
||||
? html`
|
||||
<div class="legs-grid">
|
||||
${call.legs.map(
|
||||
(leg) => html`
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge" style="${legTypeBadgeStyle(leg.type)}">
|
||||
${LEG_TYPE_LABELS[leg.type] || leg.type}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" style="${stateBadgeStyle(leg.state)}">
|
||||
${leg.state}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
${leg.remoteMedia || '--'}
|
||||
</td>
|
||||
<td>${leg.rtpPort ?? '--'}</td>
|
||||
<td>
|
||||
${leg.codec || '--'}${leg.transcoding ? ' (transcode)' : ''}
|
||||
</td>
|
||||
<td>${leg.pktReceived}</td>
|
||||
<td>${leg.pktSent}</td>
|
||||
<td>
|
||||
<div class="leg-card">
|
||||
<div class="leg-card-top">
|
||||
<div class="leg-card-badges">
|
||||
<span class="badge" style="${legTypeBadgeStyle(leg.type)}">
|
||||
${LEG_TYPE_LABELS[leg.type] || leg.type}
|
||||
</span>
|
||||
<span class="badge" style="${stateBadgeStyle(leg.state)}">
|
||||
${STATE_LABELS[leg.state] || leg.state}
|
||||
</span>
|
||||
</div>
|
||||
<div class="leg-card-id">${leg.id}</div>
|
||||
</div>
|
||||
|
||||
<div class="leg-facts">
|
||||
<div class="leg-fact">
|
||||
<span class="leg-fact-label">Codec</span>
|
||||
<span class="leg-fact-value">${leg.codec || '--'}${leg.transcoding ? ' (transcode)' : ''}</span>
|
||||
</div>
|
||||
<div class="leg-fact">
|
||||
<span class="leg-fact-label">RTP Port</span>
|
||||
<span class="leg-fact-value">${leg.rtpPort ?? '--'}</span>
|
||||
</div>
|
||||
<div class="leg-fact leg-fact-wide">
|
||||
<span class="leg-fact-label">Remote Media</span>
|
||||
<span class="leg-fact-value">${leg.remoteMedia || '--'}</span>
|
||||
</div>
|
||||
<div class="leg-fact">
|
||||
<span class="leg-fact-label">Packets In</span>
|
||||
<span class="leg-fact-value">${leg.pktReceived}</span>
|
||||
</div>
|
||||
<div class="leg-fact">
|
||||
<span class="leg-fact-label">Packets Out</span>
|
||||
<span class="leg-fact-value">${leg.pktSent}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="leg-actions">
|
||||
<button
|
||||
class="btn btn-remove"
|
||||
@click=${() => this.handleRemoveLeg(call, leg)}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
: html`<div style="color:#64748b;font-size:.75rem;font-style:italic;margin-bottom:8px;">
|
||||
No legs
|
||||
</div>`}
|
||||
</div>
|
||||
`
|
||||
: html`<div class="no-legs">No legs reported yet. SIP/system legs should appear here as soon as the call is wired.</div>`}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<button class="btn btn-primary" @click=${() => this.handleAddParticipant(call)}>
|
||||
|
||||
Reference in New Issue
Block a user