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>
627 lines
20 KiB
JavaScript
627 lines
20 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require dom';
|
|
'require poll';
|
|
'require ui';
|
|
'require uci';
|
|
'require form';
|
|
'require jabber.api as api';
|
|
|
|
return view.extend({
|
|
handleAction: function(action, args) {
|
|
var self = this;
|
|
var btn = document.activeElement;
|
|
|
|
ui.showModal(_('Please wait...'), [
|
|
E('p', { 'class': 'spinning' }, _('Processing request...'))
|
|
]);
|
|
|
|
var promise;
|
|
switch(action) {
|
|
case 'start':
|
|
promise = api.start();
|
|
break;
|
|
case 'stop':
|
|
promise = api.stop();
|
|
break;
|
|
case 'install':
|
|
promise = api.install();
|
|
break;
|
|
case 'uninstall':
|
|
if (!confirm(_('This will remove the Jabber container. User data will be preserved. Continue?')))
|
|
return ui.hideModal();
|
|
promise = api.uninstall();
|
|
break;
|
|
case 'update':
|
|
promise = api.update();
|
|
break;
|
|
case 'configure_haproxy':
|
|
promise = api.configureHaproxy();
|
|
break;
|
|
case 'emancipate':
|
|
var domain = args;
|
|
if (!domain) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Domain is required')), 'error');
|
|
return;
|
|
}
|
|
promise = api.emancipate(domain);
|
|
break;
|
|
case 'user_add':
|
|
var jid = args.jid;
|
|
var password = args.password;
|
|
if (!jid) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('JID is required')), 'error');
|
|
return;
|
|
}
|
|
promise = api.userAdd(jid, password);
|
|
break;
|
|
case 'user_del':
|
|
if (!confirm(_('Delete user ') + args + '?'))
|
|
return ui.hideModal();
|
|
promise = api.userDel(args);
|
|
break;
|
|
case 'jingle_enable':
|
|
var stunServer = args || 'stun.l.google.com:19302';
|
|
promise = api.jingleEnable(stunServer);
|
|
break;
|
|
case 'jingle_disable':
|
|
promise = api.jingleDisable();
|
|
break;
|
|
case 'sms_config':
|
|
var sender = args;
|
|
if (!sender) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Sender name is required')), 'error');
|
|
return;
|
|
}
|
|
promise = api.smsConfig(sender);
|
|
break;
|
|
case 'sms_send':
|
|
if (!args.to || !args.message) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Phone number and message are required')), 'error');
|
|
return;
|
|
}
|
|
promise = api.smsSend(args.to, args.message);
|
|
break;
|
|
case 'voicemail_config':
|
|
var notifyJid = args;
|
|
if (!notifyJid) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Notification JID is required')), 'error');
|
|
return;
|
|
}
|
|
promise = api.voicemailConfig(notifyJid);
|
|
break;
|
|
default:
|
|
ui.hideModal();
|
|
return;
|
|
}
|
|
|
|
promise.then(function(res) {
|
|
ui.hideModal();
|
|
if (res && res.success) {
|
|
var msg = res.message || _('Action completed');
|
|
if (res.password) {
|
|
msg += '\n' + _('Password: ') + res.password;
|
|
}
|
|
ui.addNotification(null, E('p', { 'style': 'white-space: pre-wrap;' }, msg), 'success');
|
|
self.load().then(function(data) {
|
|
dom.content(document.querySelector('#jabber-content'), self.renderContent(data));
|
|
});
|
|
} else {
|
|
ui.addNotification(null, E('p', res.error || _('Action failed')), 'error');
|
|
}
|
|
}).catch(function(e) {
|
|
ui.hideModal();
|
|
ui.addNotification(null, E('p', _('Error: ') + e.message), 'error');
|
|
});
|
|
},
|
|
|
|
load: function() {
|
|
return Promise.all([
|
|
api.status(),
|
|
api.userList(),
|
|
uci.load('jabber'),
|
|
api.jingleStatus(),
|
|
api.smsStatus(),
|
|
api.voicemailStatus()
|
|
]);
|
|
},
|
|
|
|
renderInstallWizard: function() {
|
|
var self = this;
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Jabber/XMPP Server')),
|
|
E('p', {}, _('Prosody is a modern XMPP server written in Lua. It aims to be easy to set up and configure, and efficient with system resources.')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('h4', {}, _('Features')),
|
|
E('ul', {}, [
|
|
E('li', {}, _('Secure messaging with end-to-end encryption (OMEMO)')),
|
|
E('li', {}, _('Multi-user chat rooms (MUC)')),
|
|
E('li', {}, _('File sharing with HTTP upload')),
|
|
E('li', {}, _('Server-to-server federation (S2S)')),
|
|
E('li', {}, _('BOSH and WebSocket for web clients')),
|
|
E('li', {}, _('Message archiving (MAM)'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('h4', {}, _('Compatible Clients')),
|
|
E('ul', {}, [
|
|
E('li', {}, _('Conversations (Android)')),
|
|
E('li', {}, _('Monal (iOS/macOS)')),
|
|
E('li', {}, _('Gajim (Windows/Linux)')),
|
|
E('li', {}, _('Dino (Linux)')),
|
|
E('li', {}, _('Converse.js (Web)'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-page-actions' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() { self.handleAction('install'); }
|
|
}, _('Install Jabber/XMPP'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
renderStatusBadge: function(running) {
|
|
var color = running === 'true' ? '#4CAF50' : '#f44336';
|
|
var text = running === 'true' ? _('Running') : _('Stopped');
|
|
return E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + color
|
|
}, text);
|
|
},
|
|
|
|
renderContent: function(data) {
|
|
var self = this;
|
|
var status = data[0] || {};
|
|
var userListData = data[1] || {};
|
|
var jingleStatus = data[3] || {};
|
|
var smsStatus = data[4] || {};
|
|
var voicemailStatus = data[5] || {};
|
|
|
|
if (status.container_state === 'not_installed') {
|
|
return this.renderInstallWizard();
|
|
}
|
|
|
|
var running = status.running === 'true';
|
|
var haproxyConfigured = status.haproxy === '1';
|
|
var domain = status.domain || '';
|
|
var hostname = status.hostname || 'jabber.local';
|
|
|
|
// Parse user list
|
|
var users = [];
|
|
if (userListData.users) {
|
|
users = userListData.users.split(',').filter(function(u) { return u.length > 0; });
|
|
}
|
|
|
|
var accessUrl = '';
|
|
if (running) {
|
|
if (domain && haproxyConfigured) {
|
|
accessUrl = 'https://' + domain;
|
|
} else {
|
|
accessUrl = 'http://192.168.255.1:' + (status.http_port || '5280');
|
|
}
|
|
}
|
|
|
|
return E('div', { 'class': 'cbi-section' }, [
|
|
E('h3', {}, _('Jabber/XMPP Server (Prosody)')),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Status')),
|
|
E('div', { 'class': 'cbi-value-field' }, this.renderStatusBadge(status.running))
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Hostname')),
|
|
E('div', { 'class': 'cbi-value-field' }, hostname)
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('C2S Port')),
|
|
E('div', { 'class': 'cbi-value-field' }, status.c2s_port || '5222')
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('S2S Port')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
status.s2s_port || '5269',
|
|
' ',
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:2px 8px;border-radius:3px;color:#fff;background:' + (status.s2s_enabled === '1' ? '#4CAF50' : '#9e9e9e')
|
|
}, status.s2s_enabled === '1' ? _('Federation ON') : _('Federation OFF'))
|
|
])
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('HTTP/BOSH Port')),
|
|
E('div', { 'class': 'cbi-value-field' }, status.http_port || '5280')
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Users')),
|
|
E('div', { 'class': 'cbi-value-field' }, status.user_count || '0')
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('MUC (Chat Rooms)')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (status.muc_enabled === '1' ? '#4CAF50' : '#9e9e9e')
|
|
}, status.muc_enabled === '1' ? _('Enabled') : _('Disabled'))
|
|
])
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('HAProxy')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (haproxyConfigured ? '#4CAF50' : '#9e9e9e')
|
|
}, haproxyConfigured ? _('Configured') : _('Not configured')),
|
|
' ',
|
|
!haproxyConfigured ? E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() { self.handleAction('configure_haproxy'); }
|
|
}, _('Configure')) : ''
|
|
])
|
|
]),
|
|
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
|
E('div', { 'class': 'cbi-value-field' }, domain || _('Not configured'))
|
|
]),
|
|
|
|
E('hr'),
|
|
|
|
E('h4', {}, _('Service Controls')),
|
|
E('div', { 'class': 'cbi-page-actions', 'style': 'margin-bottom: 20px;' }, [
|
|
running ?
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-negative',
|
|
'click': function() { self.handleAction('stop'); }
|
|
}, _('Stop')) :
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() { self.handleAction('start'); }
|
|
}, _('Start')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() { self.handleAction('update'); }
|
|
}, _('Update')),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-negative',
|
|
'click': function() { self.handleAction('uninstall'); }
|
|
}, _('Uninstall'))
|
|
]),
|
|
|
|
E('hr'),
|
|
|
|
E('h4', {}, _('User Management')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('New User')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'new-user-jid',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': 'user@' + hostname,
|
|
'style': 'width: 200px;'
|
|
}),
|
|
' ',
|
|
E('input', {
|
|
'type': 'password',
|
|
'id': 'new-user-password',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': _('Password (auto-generate if empty)'),
|
|
'style': 'width: 200px;'
|
|
}),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() {
|
|
var jid = document.getElementById('new-user-jid').value;
|
|
var password = document.getElementById('new-user-password').value;
|
|
self.handleAction('user_add', { jid: jid, password: password });
|
|
}
|
|
}, _('Add User'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Registered Users')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
users.length > 0 ?
|
|
E('table', { 'class': 'table', 'style': 'width: auto;' }, [
|
|
E('tr', { 'class': 'tr table-titles' }, [
|
|
E('th', { 'class': 'th' }, _('JID')),
|
|
E('th', { 'class': 'th' }, _('Actions'))
|
|
])
|
|
].concat(users.map(function(user) {
|
|
return E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, user),
|
|
E('td', { 'class': 'td' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-remove',
|
|
'click': function() { self.handleAction('user_del', user); }
|
|
}, _('Delete'))
|
|
])
|
|
]);
|
|
}))) :
|
|
E('em', {}, _('No users registered'))
|
|
])
|
|
]),
|
|
|
|
E('hr'),
|
|
|
|
E('h4', {}, _('Emancipate (Public Exposure)')),
|
|
E('p', {}, _('Make Jabber publicly accessible with SSL certificate, DNS records, and S2S federation.')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Domain')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'emancipate-domain',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': 'xmpp.example.com',
|
|
'value': domain
|
|
})
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-page-actions' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-action',
|
|
'click': function() {
|
|
var domainInput = document.getElementById('emancipate-domain');
|
|
self.handleAction('emancipate', domainInput.value);
|
|
}
|
|
}, _('Emancipate'))
|
|
]),
|
|
E('p', { 'style': 'font-size: 12px; color: #666;' }, [
|
|
_('DNS records needed: A record for domain, SRV records for _xmpp-client._tcp and _xmpp-server._tcp')
|
|
]),
|
|
|
|
E('hr'),
|
|
|
|
E('h4', {}, _('Connection Info')),
|
|
E('div', { 'style': 'background: #f5f5f5; padding: 15px; border-radius: 4px; font-family: monospace;' }, [
|
|
E('p', {}, [
|
|
E('strong', {}, _('XMPP Server: ')),
|
|
hostname + ':' + (status.c2s_port || '5222')
|
|
]),
|
|
E('p', {}, [
|
|
E('strong', {}, _('BOSH URL: ')),
|
|
accessUrl + '/http-bind'
|
|
]),
|
|
E('p', {}, [
|
|
E('strong', {}, _('WebSocket: ')),
|
|
(domain && haproxyConfigured ? 'wss://' + domain : 'ws://192.168.255.1:' + (status.http_port || '5280')) + '/xmpp-websocket'
|
|
]),
|
|
E('p', {}, [
|
|
E('strong', {}, _('Admin JID: ')),
|
|
(status.admin_user || 'admin') + '@' + hostname
|
|
])
|
|
]),
|
|
|
|
E('hr'),
|
|
|
|
// VoIP Integration - Jingle
|
|
E('h4', {}, _('VoIP Integration - Jingle')),
|
|
E('p', {}, _('Enable Jingle for voice/video calls between XMPP clients (Conversations, Dino, etc.)')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Status')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (jingleStatus.enabled === '1' ? '#4CAF50' : '#9e9e9e')
|
|
}, jingleStatus.enabled === '1' ? _('Enabled') : _('Disabled')),
|
|
' ',
|
|
jingleStatus.enabled === '1' ?
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-negative',
|
|
'click': function() { self.handleAction('jingle_disable'); }
|
|
}, _('Disable')) :
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-positive',
|
|
'click': function() {
|
|
var stunServer = document.getElementById('jingle-stun').value || 'stun.l.google.com:19302';
|
|
self.handleAction('jingle_enable', stunServer);
|
|
}
|
|
}, _('Enable'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('STUN Server')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'jingle-stun',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': 'stun.l.google.com:19302',
|
|
'value': jingleStatus.stun_server || 'stun.l.google.com:19302',
|
|
'style': 'width: 250px;'
|
|
})
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('TURN Server')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
jingleStatus.turn_server || E('em', {}, _('Not configured')),
|
|
E('p', { 'style': 'font-size: 12px; color: #666; margin: 5px 0 0;' },
|
|
_('TURN server for NAT traversal. Configure in /etc/config/jabber'))
|
|
])
|
|
]),
|
|
|
|
E('hr'),
|
|
|
|
// VoIP Integration - SMS Relay
|
|
E('h4', {}, _('VoIP Integration - SMS Relay')),
|
|
E('p', {}, _('Send SMS messages via OVH API through XMPP. Requires OVH API credentials in VoIP settings.')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Status')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (smsStatus.enabled === '1' ? '#4CAF50' : '#9e9e9e')
|
|
}, smsStatus.enabled === '1' ? _('Enabled') : _('Disabled')),
|
|
' ',
|
|
smsStatus.ovh_configured === '1' ?
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:#2196F3'
|
|
}, _('OVH API Configured')) :
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:#ff9800'
|
|
}, _('OVH API Not Configured'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Sender Name')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'sms-sender',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': 'SecuBox',
|
|
'value': smsStatus.sender || 'SecuBox',
|
|
'style': 'width: 200px;'
|
|
}),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() {
|
|
var sender = document.getElementById('sms-sender').value;
|
|
self.handleAction('sms_config', sender);
|
|
}
|
|
}, _('Save'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Test SMS')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', {
|
|
'type': 'tel',
|
|
'id': 'sms-test-to',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': '+33612345678',
|
|
'style': 'width: 150px;'
|
|
}),
|
|
' ',
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'sms-test-msg',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': _('Test message'),
|
|
'style': 'width: 200px;'
|
|
}),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-action',
|
|
'click': function() {
|
|
var to = document.getElementById('sms-test-to').value;
|
|
var message = document.getElementById('sms-test-msg').value;
|
|
self.handleAction('sms_send', { to: to, message: message });
|
|
}
|
|
}, _('Send Test SMS'))
|
|
])
|
|
]),
|
|
E('p', { 'style': 'font-size: 12px; color: #666;' },
|
|
_('To send SMS via XMPP, message sms@[domain] with format: +33612345678 Your message')),
|
|
|
|
E('hr'),
|
|
|
|
// VoIP Integration - Voicemail Notifications
|
|
E('h4', {}, _('VoIP Integration - Voicemail Notifications')),
|
|
E('p', {}, _('Receive XMPP notifications when new voicemails arrive in Asterisk PBX.')),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Status')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('span', {
|
|
'style': 'display:inline-block;padding:3px 10px;border-radius:3px;color:#fff;background:' + (voicemailStatus.enabled === '1' ? '#4CAF50' : '#9e9e9e')
|
|
}, voicemailStatus.enabled === '1' ? _('Enabled') : _('Disabled'))
|
|
])
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('AMI Host')),
|
|
E('div', { 'class': 'cbi-value-field' }, voicemailStatus.ami_host || '127.0.0.1')
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('AMI Port')),
|
|
E('div', { 'class': 'cbi-value-field' }, voicemailStatus.ami_port || '5038')
|
|
]),
|
|
E('div', { 'class': 'cbi-value' }, [
|
|
E('label', { 'class': 'cbi-value-title' }, _('Notification JID')),
|
|
E('div', { 'class': 'cbi-value-field' }, [
|
|
E('input', {
|
|
'type': 'text',
|
|
'id': 'voicemail-jid',
|
|
'class': 'cbi-input-text',
|
|
'placeholder': 'admin@' + hostname,
|
|
'value': voicemailStatus.notify_jid || '',
|
|
'style': 'width: 250px;'
|
|
}),
|
|
' ',
|
|
E('button', {
|
|
'class': 'btn cbi-button cbi-button-action',
|
|
'click': function() {
|
|
var notifyJid = document.getElementById('voicemail-jid').value;
|
|
self.handleAction('voicemail_config', notifyJid);
|
|
}
|
|
}, _('Configure'))
|
|
])
|
|
]),
|
|
E('p', { 'style': 'font-size: 12px; color: #666;' },
|
|
_('Requires VoIP container running with Asterisk AMI enabled.')),
|
|
|
|
E('hr'),
|
|
|
|
E('h4', {}, _('Logs')),
|
|
E('div', { 'id': 'jabber-logs' }, [
|
|
E('pre', {
|
|
'style': 'background:#1e1e1e;color:#d4d4d4;padding:10px;max-height:300px;overflow:auto;font-size:12px;border-radius:4px;'
|
|
}, _('Loading logs...'))
|
|
]),
|
|
E('div', { 'class': 'cbi-page-actions' }, [
|
|
E('button', {
|
|
'class': 'btn cbi-button',
|
|
'click': function() {
|
|
api.logs(100).then(function(res) {
|
|
var logsEl = document.querySelector('#jabber-logs pre');
|
|
if (logsEl) {
|
|
logsEl.textContent = res.logs || _('No logs available');
|
|
}
|
|
});
|
|
}
|
|
}, _('Refresh Logs'))
|
|
])
|
|
]);
|
|
},
|
|
|
|
render: function(data) {
|
|
var self = this;
|
|
|
|
var content = E('div', { 'id': 'jabber-content' }, this.renderContent(data));
|
|
|
|
// Load logs initially
|
|
api.logs(50).then(function(res) {
|
|
var logsEl = document.querySelector('#jabber-logs pre');
|
|
if (logsEl) {
|
|
logsEl.textContent = res.logs || _('No logs available');
|
|
}
|
|
});
|
|
|
|
// Poll for status updates
|
|
poll.add(function() {
|
|
return api.status().then(function(status) {
|
|
// Update status badge if needed
|
|
});
|
|
}, 10);
|
|
|
|
return content;
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|