secubox-openwrt/package/secubox/luci-app-voip/htdocs/luci-static/resources/view/voip/dialer.js
CyberMind-FR e416fa14a6 feat(jabber): Add VoIP integration to LuCI dashboard
Add Jingle VoIP, SMS Relay, and Voicemail Notifications sections to
the Jabber overview.js. Expose 9 new RPC methods in api.js for VoIP
control. Also includes remaining VoIP package updates (dialer view,
asterisk-config.sh) from previous session.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-19 14:37:17 +01:00

170 lines
6.5 KiB
JavaScript

'use strict';
'require view';
'require ui';
'require uci';
'require voip.api as api';
return view.extend({
load: function() {
return Promise.all([
uci.load('voip'),
api.getExtensions(),
api.getCalls()
]);
},
render: function(data) {
var extensions = data[1] || [];
var calls = data[2] || [];
var extOptions = extensions.map(function(ext) {
return E('option', { 'value': ext.number }, ext.number + ' - ' + ext.name);
});
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, 'Click to Call'),
E('p', { 'class': 'cbi-map-descr' },
'Make calls by selecting your extension and entering the destination number. ' +
'Your phone will ring first, then connect to the destination.'),
E('div', { 'class': 'cbi-section' }, [
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Your Extension'),
E('div', { 'class': 'cbi-value-field' }, [
E('select', { 'id': 'from-ext', 'class': 'cbi-input-select' }, extOptions)
])
]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, 'Destination Number'),
E('div', { 'class': 'cbi-value-field' }, [
E('input', {
'type': 'tel',
'id': 'to-number',
'class': 'cbi-input-text',
'placeholder': '+33612345678',
'style': 'font-size: 1.5em; padding: 10px;'
})
])
]),
E('div', { 'class': 'cbi-value' }, [
E('div', { 'class': 'cbi-value-field' }, [
E('button', {
'class': 'btn cbi-button cbi-button-positive',
'style': 'font-size: 1.2em; padding: 15px 40px;',
'click': ui.createHandlerFn(this, 'handleCall')
}, 'Call')
])
])
]),
// Dialpad
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Dialpad'),
E('div', { 'id': 'dialpad', 'style': 'display: grid; grid-template-columns: repeat(3, 80px); gap: 10px; justify-content: center;' },
['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'].map(function(digit) {
return E('button', {
'class': 'btn',
'style': 'font-size: 1.5em; padding: 20px;',
'click': function() {
var input = document.getElementById('to-number');
input.value += digit;
}
}, digit);
})
),
E('div', { 'style': 'text-align: center; margin-top: 10px;' }, [
E('button', {
'class': 'btn cbi-button',
'click': function() {
var input = document.getElementById('to-number');
input.value = input.value.slice(0, -1);
}
}, 'Backspace'),
' ',
E('button', {
'class': 'btn cbi-button',
'click': function() {
document.getElementById('to-number').value = '';
}
}, 'Clear')
])
]),
// Active calls
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, 'Active Calls'),
this.renderCallsTable(calls)
])
]);
return view;
},
renderCallsTable: function(calls) {
if (!calls || calls.length === 0) {
return E('p', { 'class': 'cbi-value-description' }, 'No active calls');
}
return E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, 'Channel'),
E('th', { 'class': 'th' }, 'State'),
E('th', { 'class': 'th' }, 'Caller'),
E('th', { 'class': 'th' }, 'Connected'),
E('th', { 'class': 'th' }, 'Duration'),
E('th', { 'class': 'th' }, 'Actions')
])
].concat(calls.map(function(call) {
return E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, call.channel),
E('td', { 'class': 'td' }, call.state),
E('td', { 'class': 'td' }, call.caller),
E('td', { 'class': 'td' }, call.connected),
E('td', { 'class': 'td' }, call.duration),
E('td', { 'class': 'td' }, [
E('button', {
'class': 'btn cbi-button cbi-button-remove',
'click': ui.createHandlerFn(this, 'handleHangup', call.channel)
}, 'Hangup')
])
]);
}, this)));
},
handleCall: function() {
var fromExt = document.getElementById('from-ext').value;
var toNumber = document.getElementById('to-number').value;
if (!fromExt) {
ui.addNotification(null, E('p', 'Please select your extension'), 'error');
return;
}
if (!toNumber) {
ui.addNotification(null, E('p', 'Please enter a destination number'), 'error');
return;
}
ui.showModal('Calling...', [
E('p', {}, 'Connecting ' + fromExt + ' to ' + toNumber + '...'),
E('p', {}, 'Your phone will ring first.')
]);
return api.originateCall(fromExt, toNumber).then(function(res) {
ui.hideModal();
if (res.success) {
ui.addNotification(null, E('p', 'Call initiated'), 'success');
} else {
ui.addNotification(null, E('p', 'Call failed: ' + res.output), 'error');
}
});
},
handleHangup: function(channel) {
return api.hangupCall(channel).then(function(res) {
ui.addNotification(null, E('p', 'Call terminated'), 'success');
window.location.reload();
});
}
});