secubox-openwrt/package/secubox/luci-app-turn/htdocs/luci-static/resources/view/turn/overview.js
CyberMind-FR df58e96a9a feat(turn): Add setup-nextcloud command for Nextcloud Talk
- turnctl setup-nextcloud [turn-domain] [use-port-443]
  - Configures TURN for Nextcloud Talk compatibility
  - Uses port 443 by default (firewall-friendly)
  - Generates auth secret if not exists
  - Outputs admin settings to paste into Nextcloud Talk

- LuCI integration:
  - New "Nextcloud Talk" section in TURN overview
  - Shows STUN/TURN/secret settings for easy copy-paste
  - RPC method: setup_nextcloud

- ACL updated with setup_nextcloud permission

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-21 18:11:42 +01:00

256 lines
11 KiB
JavaScript

'use strict';
'require view';
'require rpc';
'require poll';
'require ui';
var callStatus = rpc.declare({ object: 'luci.turn', method: 'status', expect: {} });
var callStart = rpc.declare({ object: 'luci.turn', method: 'start', expect: {} });
var callStop = rpc.declare({ object: 'luci.turn', method: 'stop', expect: {} });
var callEnable = rpc.declare({ object: 'luci.turn', method: 'enable', expect: {} });
var callDisable = rpc.declare({ object: 'luci.turn', method: 'disable', expect: {} });
var callSetupJitsi = rpc.declare({ object: 'luci.turn', method: 'setup_jitsi', params: ['jitsi_domain', 'turn_domain'], expect: {} });
var callSetupNextcloud = rpc.declare({ object: 'luci.turn', method: 'setup_nextcloud', params: ['turn_domain', 'use_port_443'], expect: {} });
var callSSL = rpc.declare({ object: 'luci.turn', method: 'ssl', params: ['domain'], expect: {} });
var callExpose = rpc.declare({ object: 'luci.turn', method: 'expose', params: ['domain'], expect: {} });
var callCredentials = rpc.declare({ object: 'luci.turn', method: 'credentials', params: ['username', 'ttl'], expect: {} });
var callLogs = rpc.declare({ object: 'luci.turn', method: 'logs', params: ['lines'], expect: {} });
return view.extend({
data: {},
load: function() {
return callStatus().then(function(r) { this.data = r; return r; }.bind(this));
},
render: function(data) {
var self = this;
this.data = data || {};
poll.add(function() {
return callStatus().then(function(r) {
self.data = r;
self.updateUI(r);
});
}, 5);
return E('div', { 'class': 'cbi-map' }, [
E('style', {}, this.getStyles()),
E('div', { 'class': 'sb-header' }, [
E('h2', {}, 'TURN Server'),
E('div', { 'class': 'sb-chips' }, [
E('span', { 'class': 'chip', 'id': 'chip-status' },
this.data.running ? 'Running' : 'Stopped'),
E('span', { 'class': 'chip' }, 'Realm: ' + (this.data.realm || 'N/A')),
E('span', { 'class': 'chip' }, 'Port: ' + (this.data.port || 3478))
])
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'Service Control'),
E('div', { 'class': 'btn-row' }, [
E('button', { 'class': 'sb-btn sb-btn-success', 'click': ui.createHandlerFn(this, 'handleStart') }, 'Start'),
E('button', { 'class': 'sb-btn sb-btn-danger', 'click': ui.createHandlerFn(this, 'handleStop') }, 'Stop'),
E('button', { 'class': 'sb-btn', 'click': ui.createHandlerFn(this, 'handleEnable') }, 'Enable Autostart'),
E('button', { 'class': 'sb-btn', 'click': ui.createHandlerFn(this, 'handleDisable') }, 'Disable Autostart')
])
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'Status'),
E('div', { 'class': 'sb-grid' }, [
this.renderCard('Server', this.data.running ? 'Running' : 'Stopped', this.data.running ? 'success' : 'danger'),
this.renderCard('UDP 3478', this.data.udp_3478 ? 'Listening' : 'Closed', this.data.udp_3478 ? 'success' : 'warning'),
this.renderCard('TCP 5349', this.data.tcp_5349 ? 'Listening' : 'Closed', this.data.tcp_5349 ? 'success' : 'warning'),
this.renderCard('External IP', this.data.external_ip || this.data.detected_ip || 'Unknown', 'info')
])
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'Jitsi Integration'),
E('p', {}, 'Configure TURN server for Jitsi Meet WebRTC connections'),
E('div', { 'class': 'form-row' }, [
E('input', { 'type': 'text', 'id': 'jitsi-domain', 'placeholder': 'jitsi.secubox.in', 'class': 'sb-input' }),
E('input', { 'type': 'text', 'id': 'turn-domain', 'placeholder': 'turn.secubox.in', 'class': 'sb-input' }),
E('button', { 'class': 'sb-btn sb-btn-primary', 'click': ui.createHandlerFn(this, 'handleSetupJitsi') }, 'Setup for Jitsi')
])
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'Nextcloud Talk'),
E('p', {}, 'Configure TURN for Nextcloud Talk (uses port 443 for firewall compatibility)'),
E('div', { 'class': 'form-row' }, [
E('input', { 'type': 'text', 'id': 'nc-turn-domain', 'placeholder': 'turn.secubox.in', 'class': 'sb-input' }),
E('button', { 'class': 'sb-btn sb-btn-primary', 'click': ui.createHandlerFn(this, 'handleSetupNextcloud') }, 'Setup for Nextcloud')
]),
E('pre', { 'id': 'nextcloud-output', 'class': 'sb-output', 'style': 'display:none;' }, '')
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'SSL & Expose'),
E('div', { 'class': 'form-row' }, [
E('input', { 'type': 'text', 'id': 'ssl-domain', 'placeholder': 'turn.secubox.in', 'class': 'sb-input' }),
E('button', { 'class': 'sb-btn', 'click': ui.createHandlerFn(this, 'handleSSL') }, 'Setup SSL'),
E('button', { 'class': 'sb-btn sb-btn-primary', 'click': ui.createHandlerFn(this, 'handleExpose') }, 'Expose (DNS+FW)')
])
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'Generate Credentials'),
E('p', {}, 'Generate time-limited TURN credentials for WebRTC clients'),
E('div', { 'class': 'form-row' }, [
E('input', { 'type': 'text', 'id': 'cred-user', 'placeholder': 'username', 'value': 'webrtc', 'class': 'sb-input' }),
E('input', { 'type': 'number', 'id': 'cred-ttl', 'placeholder': 'TTL (seconds)', 'value': '86400', 'class': 'sb-input' }),
E('button', { 'class': 'sb-btn sb-btn-primary', 'click': ui.createHandlerFn(this, 'handleCredentials') }, 'Generate')
]),
E('pre', { 'id': 'credentials-output', 'class': 'sb-output', 'style': 'display:none;' }, '')
]),
E('div', { 'class': 'sb-section' }, [
E('h3', {}, 'Logs'),
E('button', { 'class': 'sb-btn', 'click': ui.createHandlerFn(this, 'handleShowLogs') }, 'Show Logs'),
E('pre', { 'id': 'logs-output', 'class': 'sb-output', 'style': 'display:none; max-height:300px; overflow:auto;' }, '')
])
]);
},
renderCard: function(title, value, status) {
var statusClass = status === 'success' ? 'card-success' : (status === 'danger' ? 'card-danger' : (status === 'warning' ? 'card-warning' : 'card-info'));
return E('div', { 'class': 'sb-card ' + statusClass }, [
E('div', { 'class': 'card-title' }, title),
E('div', { 'class': 'card-value' }, value)
]);
},
updateUI: function(data) {
var chip = document.getElementById('chip-status');
if (chip) {
chip.textContent = data.running ? 'Running' : 'Stopped';
chip.className = 'chip ' + (data.running ? 'chip-success' : 'chip-danger');
}
},
handleStart: function() {
return callStart().then(function() {
ui.addNotification(null, E('p', 'TURN server started'));
});
},
handleStop: function() {
return callStop().then(function() {
ui.addNotification(null, E('p', 'TURN server stopped'));
});
},
handleEnable: function() {
return callEnable().then(function() {
ui.addNotification(null, E('p', 'TURN server enabled'));
});
},
handleDisable: function() {
return callDisable().then(function() {
ui.addNotification(null, E('p', 'TURN server disabled'));
});
},
handleSetupJitsi: function() {
var jitsiDomain = document.getElementById('jitsi-domain').value || 'jitsi.secubox.in';
var turnDomain = document.getElementById('turn-domain').value || 'turn.secubox.in';
return callSetupJitsi(jitsiDomain, turnDomain).then(function(res) {
ui.addNotification(null, E('p', 'TURN configured for Jitsi. Auth secret: ' + (res.auth_secret || 'generated')));
});
},
handleSetupNextcloud: function() {
var turnDomain = document.getElementById('nc-turn-domain').value || 'turn.secubox.in';
return callSetupNextcloud(turnDomain, 'yes').then(function(res) {
var output = document.getElementById('nextcloud-output');
output.style.display = 'block';
output.textContent = 'Nextcloud Talk Admin Settings:\n\n' +
'STUN servers: ' + turnDomain + ':' + (res.stun_port || 3478) + '\n' +
'TURN server: ' + turnDomain + ':' + (res.tls_port || 443) + '\n' +
'TURN secret: ' + (res.auth_secret || '') + '\n' +
'Protocol: UDP and TCP\n\n' +
'Note: Do NOT add turn:// or turns:// prefix';
ui.addNotification(null, E('p', 'TURN configured for Nextcloud Talk on port ' + (res.tls_port || 443)));
});
},
handleSSL: function() {
var domain = document.getElementById('ssl-domain').value || 'turn.secubox.in';
return callSSL(domain).then(function(res) {
ui.addNotification(null, E('p', 'SSL configured for ' + domain));
});
},
handleExpose: function() {
var domain = document.getElementById('ssl-domain').value || 'turn.secubox.in';
return callExpose(domain).then(function(res) {
ui.addNotification(null, E('p', 'TURN exposed on ' + domain));
});
},
handleCredentials: function() {
var username = document.getElementById('cred-user').value || 'webrtc';
var ttl = parseInt(document.getElementById('cred-ttl').value) || 86400;
return callCredentials(username, ttl).then(function(res) {
var output = document.getElementById('credentials-output');
output.style.display = 'block';
output.textContent = JSON.stringify({
urls: ['turn:' + res.realm + ':3478', 'turn:' + res.realm + ':3478?transport=tcp'],
username: res.username,
credential: res.password
}, null, 2);
});
},
handleShowLogs: function() {
return callLogs(100).then(function(res) {
var output = document.getElementById('logs-output');
output.style.display = 'block';
output.textContent = res.logs || 'No logs available';
});
},
getStyles: function() {
return [
'.sb-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:20px; }',
'.sb-chips { display:flex; gap:10px; }',
'.chip { padding:5px 12px; border-radius:15px; font-size:0.85em; background:#444; color:#fff; }',
'.chip-success { background:#28a745; }',
'.chip-danger { background:#dc3545; }',
'.sb-section { background:#1a1a2e; padding:20px; margin-bottom:15px; border-radius:8px; }',
'.sb-section h3 { margin:0 0 15px 0; color:#4fc3f7; }',
'.sb-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:15px; }',
'.sb-card { padding:15px; border-radius:8px; text-align:center; }',
'.card-success { background:#155724; border:1px solid #28a745; }',
'.card-danger { background:#721c24; border:1px solid #dc3545; }',
'.card-warning { background:#856404; border:1px solid #ffc107; }',
'.card-info { background:#0c5460; border:1px solid #17a2b8; }',
'.card-title { font-size:0.85em; color:#aaa; margin-bottom:5px; }',
'.card-value { font-size:1.1em; font-weight:bold; }',
'.btn-row { display:flex; gap:10px; flex-wrap:wrap; }',
'.sb-btn { padding:8px 16px; border:none; border-radius:5px; cursor:pointer; background:#444; color:#fff; }',
'.sb-btn:hover { background:#555; }',
'.sb-btn-primary { background:#007bff; }',
'.sb-btn-primary:hover { background:#0056b3; }',
'.sb-btn-success { background:#28a745; }',
'.sb-btn-success:hover { background:#218838; }',
'.sb-btn-danger { background:#dc3545; }',
'.sb-btn-danger:hover { background:#c82333; }',
'.form-row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }',
'.sb-input { padding:8px 12px; border:1px solid #444; border-radius:5px; background:#2a2a3e; color:#fff; }',
'.sb-output { background:#0a0a15; padding:15px; border-radius:5px; font-family:monospace; font-size:0.9em; white-space:pre-wrap; word-break:break-all; }'
].join('\n');
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});