feat: Replace RustDesk with rtty remote access in system-hub
- Add rtty support for reverse proxy terminal access to relay server - Add ttyd web console with embedded iframe terminal - Fix pgrep -x issues by replacing with pidof (BusyBox compatible) - Update API.js to v0.4.0 with rtty parameters - Rewrite remote.js view with rtty configuration UI: - Server host/port/token/description fields - SSL/TLS toggle - Connect/Disconnect controls - Device ID display (auto-generated from MAC) - Add RPCD methods: ttyd_status, ttyd_install, ttyd_start, ttyd_stop, ttyd_configure - Update ACL permissions for new methods Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
50bd0c872e
commit
fa02d44f8d
@ -6,11 +6,11 @@
|
||||
* System Hub API
|
||||
* Package: luci-app-system-hub
|
||||
* RPCD object: luci.system-hub
|
||||
* Version: 0.3.6
|
||||
* Version: 0.4.0
|
||||
*/
|
||||
|
||||
// Debug log to verify correct version is loaded
|
||||
console.log('🔧 System Hub API v0.3.6 loaded at', new Date().toISOString());
|
||||
console.log('🔧 System Hub API v0.4.0 loaded at', new Date().toISOString());
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
@ -183,7 +183,7 @@ var callRemoteInstall = rpc.declare({
|
||||
var callRemoteConfigure = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'remote_configure',
|
||||
params: ['relay_server', 'relay_key', 'rustdesk_enabled'],
|
||||
params: ['host', 'port', 'id', 'description', 'ssl', 'token'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
@ -203,7 +203,39 @@ var callRemoteServiceAction = rpc.declare({
|
||||
var callRemoteSaveSettings = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'remote_save_settings',
|
||||
params: ['allow_unattended', 'require_approval', 'notify_on_connect'],
|
||||
params: ['host', 'port', 'id', 'description', 'ssl', 'token'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
// TTYD Web Console
|
||||
var callTtydStatus = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'ttyd_status',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callTtydInstall = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'ttyd_install',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callTtydStart = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'ttyd_start',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callTtydStop = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'ttyd_stop',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callTtydConfigure = rpc.declare({
|
||||
object: 'luci.system-hub',
|
||||
method: 'ttyd_configure',
|
||||
params: ['port', 'interface'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
@ -287,5 +319,14 @@ return baseclass.extend({
|
||||
},
|
||||
remoteSaveSettings: function(data) {
|
||||
return callRemoteSaveSettings(data);
|
||||
},
|
||||
|
||||
// TTYD Web Console
|
||||
ttydStatus: callTtydStatus,
|
||||
ttydInstall: callTtydInstall,
|
||||
ttydStart: callTtydStart,
|
||||
ttydStop: callTtydStop,
|
||||
ttydConfigure: function(data) {
|
||||
return callTtydConfigure(data);
|
||||
}
|
||||
});
|
||||
|
||||
@ -14,106 +14,229 @@ Theme.init({ language: shLang });
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return API.remoteStatus();
|
||||
return Promise.all([
|
||||
API.remoteStatus(),
|
||||
API.ttydStatus()
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(remote) {
|
||||
this.remote = remote || {};
|
||||
render: function(data) {
|
||||
var remote = data[0] || {};
|
||||
var ttyd = data[1] || {};
|
||||
this.remote = remote;
|
||||
this.ttyd = ttyd;
|
||||
|
||||
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||
ThemeAssets.stylesheet('common.css'),
|
||||
ThemeAssets.stylesheet('dashboard.css'),
|
||||
HubNav.renderTabs('remote'),
|
||||
|
||||
// RustDesk Section
|
||||
|
||||
// rtty Remote Access Section
|
||||
E('div', { 'class': 'sh-card sh-remote-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🖥️'), 'RustDesk - Assistance à Distance' ]),
|
||||
E('div', { 'class': 'sh-card-badge' }, remote.enabled ? 'Actif' : 'Inactif')
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🔗'), _('rtty - Remote Terminal') ]),
|
||||
E('div', { 'class': 'sh-card-badge', 'id': 'rtty-status-badge' }, remote.running ? _('Connected') : _('Disconnected'))
|
||||
]),
|
||||
E('div', { 'class': 'sh-card-body' }, [
|
||||
// RustDesk ID
|
||||
// Device ID
|
||||
E('div', { 'class': 'sh-remote-id' }, [
|
||||
E('div', { 'class': 'sh-remote-id-icon' }, '🖥️'),
|
||||
E('div', { 'class': 'sh-remote-id-icon' }, '🆔'),
|
||||
E('div', {}, [
|
||||
E('div', { 'class': 'sh-remote-id-value', 'id': 'remote-id-value' }, remote.id || '--- --- ---'),
|
||||
E('div', { 'class': 'sh-remote-id-label' }, 'ID RustDesk - Communiquez ce code au support')
|
||||
E('div', { 'class': 'sh-remote-id-value', 'id': 'rtty-device-id' }, remote.id || _('Not configured')),
|
||||
E('div', { 'class': 'sh-remote-id-label' }, _('Device ID - Share this with support'))
|
||||
])
|
||||
]),
|
||||
|
||||
// Settings
|
||||
this.renderToggle('🔒', 'Accès sans surveillance', 'Permettre la connexion sans approbation', remote.allow_unattended, 'allow_unattended'),
|
||||
this.renderToggle('✅', 'Approbation requise', 'Confirmer chaque connexion entrante', remote.require_approval, 'require_approval'),
|
||||
this.renderToggle('🔔', 'Notification de connexion', 'Recevoir une alerte à chaque session', remote.notify_on_connect, 'notify_on_connect'),
|
||||
|
||||
|
||||
// Configuration fields
|
||||
E('div', { 'class': 'sh-form-grid', 'style': 'margin-top: 16px;' }, [
|
||||
// Server Host
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, _('Server Host')),
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'class': 'sh-form-input',
|
||||
'id': 'rtty-host',
|
||||
'placeholder': 'rttys.example.com',
|
||||
'value': remote.host || ''
|
||||
})
|
||||
]),
|
||||
// Server Port
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, _('Server Port')),
|
||||
E('input', {
|
||||
'type': 'number',
|
||||
'class': 'sh-form-input',
|
||||
'id': 'rtty-port',
|
||||
'placeholder': '5912',
|
||||
'value': remote.port || 5912
|
||||
})
|
||||
]),
|
||||
// Token
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, _('Access Token')),
|
||||
E('input', {
|
||||
'type': 'password',
|
||||
'class': 'sh-form-input',
|
||||
'id': 'rtty-token',
|
||||
'placeholder': _('Optional authentication token'),
|
||||
'value': remote.token || ''
|
||||
})
|
||||
]),
|
||||
// Device Description
|
||||
E('div', { 'class': 'sh-form-group' }, [
|
||||
E('label', { 'class': 'sh-form-label' }, _('Description')),
|
||||
E('input', {
|
||||
'type': 'text',
|
||||
'class': 'sh-form-input',
|
||||
'id': 'rtty-description',
|
||||
'placeholder': _('Device description'),
|
||||
'value': remote.description || ''
|
||||
})
|
||||
])
|
||||
]),
|
||||
|
||||
// SSL Toggle
|
||||
this.renderToggle('🔒', _('Use SSL/TLS'), _('Encrypt connection to relay server'), remote.ssl, 'rtty-ssl'),
|
||||
|
||||
// Status
|
||||
!remote.installed ? E('div', { 'style': 'padding: 16px; background: rgba(245, 158, 11, 0.1); border-radius: 10px; border-left: 3px solid #f59e0b; margin-top: 16px;' }, [
|
||||
E('span', { 'style': 'font-size: 20px; margin-right: 12px;' }, '⚠️'),
|
||||
E('span', {}, 'RustDesk n\'est pas installé. '),
|
||||
E('a', { 'href': '#', 'style': 'color: #6366f1;', 'click': L.bind(this.installRustdesk, this) }, 'Installer maintenant')
|
||||
E('span', {}, _('rtty is not installed.')),
|
||||
E('a', { 'href': '#', 'style': 'color: #6366f1; margin-left: 8px;', 'click': L.bind(this.installRtty, this) }, _('Install now'))
|
||||
]) : E('div', { 'style': 'padding: 10px; background: rgba(34,197,94,0.12); border-radius: 10px; margin-top: 16px;' }, [
|
||||
E('span', { 'style': 'font-size: 20px; margin-right: 12px;' }, remote.running ? '🟢' : '🟠'),
|
||||
E('span', {}, remote.running ? 'Service RustDesk en cours d\'exécution' : 'Service installé mais arrêté')
|
||||
E('span', {}, remote.running ? _('Connected to relay server') : _('Installed but not connected'))
|
||||
]),
|
||||
|
||||
|
||||
// Actions
|
||||
E('div', { 'class': 'sh-btn-group' }, [
|
||||
E('button', {
|
||||
E('div', { 'class': 'sh-btn-group', 'style': 'margin-top: 16px;' }, [
|
||||
E('button', {
|
||||
'class': 'sh-btn sh-btn-primary',
|
||||
'click': L.bind(this.showCredentials, this)
|
||||
}, [ '🔑 Identifiants' ]),
|
||||
E('button', {
|
||||
'id': 'rtty-save-btn',
|
||||
'click': L.bind(this.saveRttySettings, this)
|
||||
}, [ '💾 ', _('Save Settings') ]),
|
||||
E('button', {
|
||||
'class': 'sh-btn',
|
||||
'click': L.bind(this.toggleService, this)
|
||||
}, [ remote.running ? '⏹️ Arrêter' : '▶️ Démarrer' ])
|
||||
'id': 'rtty-toggle-btn',
|
||||
'click': L.bind(this.toggleRttyService, this)
|
||||
}, [ remote.running ? '⏹️ ' + _('Disconnect') : '▶️ ' + _('Connect') ]),
|
||||
E('button', {
|
||||
'class': 'sh-btn',
|
||||
'click': L.bind(this.showCredentials, this)
|
||||
}, [ '🔑 ', _('Show Credentials') ])
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
|
||||
// SSH Section
|
||||
E('div', { 'class': 'sh-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🔐'), 'Accès SSH' ])
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🔐'), _('SSH Access') ])
|
||||
]),
|
||||
E('div', { 'class': 'sh-card-body' }, [
|
||||
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Status'),
|
||||
E('span', { 'class': 'sh-sysinfo-value', 'style': 'color: #22c55e;' }, 'Actif')
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Status')),
|
||||
E('span', { 'class': 'sh-sysinfo-value', 'style': 'color: #22c55e;' }, _('Active'))
|
||||
]),
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Port'),
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Port')),
|
||||
E('span', { 'class': 'sh-sysinfo-value' }, '22')
|
||||
])
|
||||
]),
|
||||
E('div', { 'style': 'margin-top: 16px; padding: 14px; background: #0a0a0f; border-radius: 8px; font-family: monospace; font-size: 12px; color: #a0a0b0;' }, [
|
||||
'ssh root@', E('span', { 'style': 'color: #6366f1;' }, '192.168.1.1')
|
||||
'ssh root@', E('span', { 'style': 'color: #6366f1;' }, window.location.hostname)
|
||||
])
|
||||
])
|
||||
]),
|
||||
|
||||
|
||||
// Web Console (ttyd) Section
|
||||
E('div', { 'class': 'sh-card sh-webconsole-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '💻'), _('Web Console') ]),
|
||||
E('div', { 'class': 'sh-card-badge', 'id': 'ttyd-status-badge' }, ttyd.running ? _('Running') : _('Stopped'))
|
||||
]),
|
||||
E('div', { 'class': 'sh-card-body' }, [
|
||||
// Status info
|
||||
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Status')),
|
||||
E('span', { 'class': 'sh-sysinfo-value', 'id': 'ttyd-status-text', 'style': ttyd.running ? 'color: #22c55e;' : 'color: #f59e0b;' },
|
||||
ttyd.installed ? (ttyd.running ? _('Running') : _('Stopped')) : _('Not Installed'))
|
||||
]),
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Port')),
|
||||
E('span', { 'class': 'sh-sysinfo-value' }, ttyd.port || 7681)
|
||||
])
|
||||
]),
|
||||
|
||||
// Install warning or console iframe
|
||||
!ttyd.installed ? E('div', { 'style': 'padding: 16px; background: rgba(245, 158, 11, 0.1); border-radius: 10px; border-left: 3px solid #f59e0b; margin-top: 16px;' }, [
|
||||
E('span', { 'style': 'font-size: 20px; margin-right: 12px;' }, '⚠️'),
|
||||
E('span', {}, _('Web Console (ttyd) is not installed.')),
|
||||
E('a', { 'href': '#', 'style': 'color: #6366f1; margin-left: 8px;', 'click': L.bind(this.installTtyd, this) }, _('Install now'))
|
||||
]) : (ttyd.running ?
|
||||
// Console iframe when running
|
||||
E('div', { 'id': 'ttyd-console-container', 'style': 'margin-top: 16px; border-radius: 8px; overflow: hidden; background: #0a0a0f; border: 1px solid rgba(255,255,255,0.1);' }, [
|
||||
E('iframe', {
|
||||
'id': 'ttyd-iframe',
|
||||
'src': 'http://' + window.location.hostname + ':' + (ttyd.port || 7681),
|
||||
'style': 'width: 100%; height: 400px; border: none; background: #0a0a0f;',
|
||||
'title': 'Web Console'
|
||||
})
|
||||
]) :
|
||||
// Start prompt when stopped
|
||||
E('div', { 'style': 'padding: 20px; background: rgba(99, 102, 241, 0.1); border-radius: 10px; margin-top: 16px; text-align: center;' }, [
|
||||
E('span', { 'style': 'font-size: 40px; display: block; margin-bottom: 12px;' }, '💻'),
|
||||
E('p', { 'style': 'margin: 0 0 16px 0; color: #a0a0b0;' }, _('Web Console is ready. Click Start to open the terminal.')),
|
||||
E('button', {
|
||||
'class': 'sh-btn sh-btn-primary',
|
||||
'click': L.bind(this.startTtyd, this)
|
||||
}, [ '▶️ ', _('Start Console') ])
|
||||
])
|
||||
),
|
||||
|
||||
// Actions
|
||||
ttyd.installed ? E('div', { 'class': 'sh-btn-group', 'style': 'margin-top: 16px;' }, [
|
||||
E('button', {
|
||||
'class': 'sh-btn sh-btn-primary',
|
||||
'id': 'ttyd-toggle-btn',
|
||||
'click': L.bind(this.toggleTtyd, this)
|
||||
}, [ ttyd.running ? '⏹️ ' + _('Stop') : '▶️ ' + _('Start') ]),
|
||||
ttyd.running ? E('button', {
|
||||
'class': 'sh-btn',
|
||||
'click': L.bind(this.openTtydFullscreen, this)
|
||||
}, [ '🔲 ', _('Fullscreen') ]) : '',
|
||||
E('button', {
|
||||
'class': 'sh-btn',
|
||||
'click': L.bind(this.refreshTtydStatus, this)
|
||||
}, [ '🔄 ', _('Refresh') ])
|
||||
]) : ''
|
||||
])
|
||||
]),
|
||||
|
||||
// Support Contact (static)
|
||||
E('div', { 'class': 'sh-card' }, [
|
||||
E('div', { 'class': 'sh-card-header' }, [
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📞'), 'Contact Support' ])
|
||||
E('div', { 'class': 'sh-card-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '📞'), _('Contact Support') ])
|
||||
]),
|
||||
E('div', { 'class': 'sh-card-body' }, [
|
||||
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Fournisseur'),
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Provider')),
|
||||
E('span', { 'class': 'sh-sysinfo-value' }, 'CyberMind.fr')
|
||||
]),
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Email'),
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Email')),
|
||||
E('span', { 'class': 'sh-sysinfo-value' }, 'support@cybermind.fr')
|
||||
]),
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Téléphone'),
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Phone')),
|
||||
E('span', { 'class': 'sh-sysinfo-value' }, '+33 1 23 45 67 89')
|
||||
]),
|
||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Website'),
|
||||
E('span', { 'class': 'sh-sysinfo-label' }, _('Website')),
|
||||
E('span', { 'class': 'sh-sysinfo-value' }, 'https://cybermind.fr')
|
||||
])
|
||||
])
|
||||
@ -124,8 +247,8 @@ return view.extend({
|
||||
return view;
|
||||
},
|
||||
|
||||
renderToggle: function(icon, label, desc, enabled, field) {
|
||||
return E('div', { 'class': 'sh-toggle' }, [
|
||||
renderToggle: function(icon, label, desc, enabled, id) {
|
||||
return E('div', { 'class': 'sh-toggle', 'style': 'margin-top: 16px;' }, [
|
||||
E('div', { 'class': 'sh-toggle-info' }, [
|
||||
E('span', { 'class': 'sh-toggle-icon' }, icon),
|
||||
E('div', {}, [
|
||||
@ -133,63 +256,58 @@ return view.extend({
|
||||
E('div', { 'class': 'sh-toggle-desc' }, desc)
|
||||
])
|
||||
]),
|
||||
E('div', {
|
||||
E('div', {
|
||||
'class': 'sh-toggle-switch' + (enabled ? ' active' : ''),
|
||||
'data-field': field,
|
||||
'click': L.bind(function(ev) {
|
||||
'id': id,
|
||||
'click': function(ev) {
|
||||
ev.target.classList.toggle('active');
|
||||
this.saveSettings();
|
||||
}, this)
|
||||
}
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
showCredentials: function() {
|
||||
ui.showModal(_('Identifiants RustDesk'), [
|
||||
E('p', {}, 'Récupération en cours…'),
|
||||
ui.showModal(_('rtty Credentials'), [
|
||||
E('p', {}, _('Retrieving credentials...')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
API.remoteCredentials().then(function(result) {
|
||||
API.remoteCredentials().then(L.bind(function(result) {
|
||||
ui.hideModal();
|
||||
ui.showModal(_('Identifiants RustDesk'), [
|
||||
E('div', { 'style': 'font-size:18px; margin-bottom:8px;' }, 'ID: ' + (result.id || '---')),
|
||||
E('div', { 'style': 'font-size:18px;' }, 'Mot de passe: ' + (result.password || '---')),
|
||||
ui.showModal(_('rtty Credentials'), [
|
||||
E('div', { 'style': 'font-size:16px; margin-bottom:12px;' }, [
|
||||
E('strong', {}, _('Device ID:')), ' ', (result.id || _('Not configured'))
|
||||
]),
|
||||
E('div', { 'style': 'font-size:16px; margin-bottom:12px;' }, [
|
||||
E('strong', {}, _('Token:')), ' ', (result.token || _('No token set'))
|
||||
]),
|
||||
E('p', { 'style': 'color: #a0a0b0; font-size: 13px; margin-top: 16px;' },
|
||||
_('Share the Device ID with support to allow remote access via the rtty relay server.')),
|
||||
E('div', { 'class': 'sh-btn-group', 'style': 'margin-top:16px;' }, [
|
||||
E('button', { 'class': 'sh-btn sh-btn-primary', 'click': ui.hideModal }, 'Fermer')
|
||||
E('button', { 'class': 'sh-btn sh-btn-primary', 'click': ui.hideModal }, _('Close'))
|
||||
])
|
||||
]);
|
||||
}).catch(function(err) {
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
toggleService: function() {
|
||||
toggleRttyService: function() {
|
||||
if (!this.remote || !this.remote.installed) return;
|
||||
var action = this.remote.running ? 'stop' : 'start';
|
||||
API.remoteServiceAction(action).then(L.bind(function(res) {
|
||||
if (res.success) {
|
||||
this.reload();
|
||||
ui.addNotification(null, E('p', {}, '✅ ' + action), 'info');
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, res.error || 'Action impossible'), 'error');
|
||||
}
|
||||
}, this));
|
||||
},
|
||||
|
||||
installRustdesk: function(ev) {
|
||||
ev.preventDefault();
|
||||
ui.showModal(_('Installation'), [
|
||||
E('p', {}, 'Installation de RustDesk…'),
|
||||
ui.showModal(action === 'start' ? _('Connecting...') : _('Disconnecting...'), [
|
||||
E('p', {}, action === 'start' ? _('Connecting to relay server...') : _('Stopping rtty service...')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
API.remoteInstall().then(L.bind(function(result) {
|
||||
|
||||
API.remoteServiceAction(action).then(L.bind(function(res) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, result.message || 'Installé'), 'info');
|
||||
if (res.success) {
|
||||
ui.addNotification(null, E('p', {}, '✅ ' + (res.message || action)), 'info');
|
||||
this.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, result.error || 'Installation impossible'), 'error');
|
||||
ui.addNotification(null, E('p', {}, res.error || _('Action failed')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
@ -197,15 +315,60 @@ return view.extend({
|
||||
});
|
||||
},
|
||||
|
||||
saveSettings: function() {
|
||||
var allow = document.querySelector('[data-field="allow_unattended"]').classList.contains('active') ? 1 : 0;
|
||||
var require = document.querySelector('[data-field="require_approval"]').classList.contains('active') ? 1 : 0;
|
||||
var notify = document.querySelector('[data-field="notify_on_connect"]').classList.contains('active') ? 1 : 0;
|
||||
saveRttySettings: function() {
|
||||
var host = document.getElementById('rtty-host').value;
|
||||
var port = parseInt(document.getElementById('rtty-port').value) || 5912;
|
||||
var token = document.getElementById('rtty-token').value;
|
||||
var description = document.getElementById('rtty-description').value;
|
||||
var ssl = document.getElementById('rtty-ssl').classList.contains('active') ? 1 : 0;
|
||||
|
||||
if (!host) {
|
||||
ui.addNotification(null, E('p', {}, _('Server host is required')), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.showModal(_('Saving Settings'), [
|
||||
E('p', {}, _('Saving rtty configuration...')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
|
||||
API.remoteSaveSettings({
|
||||
allow_unattended: allow,
|
||||
require_approval: require,
|
||||
notify_on_connect: notify
|
||||
host: host,
|
||||
port: port,
|
||||
token: token,
|
||||
description: description,
|
||||
ssl: ssl
|
||||
}).then(L.bind(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('Settings saved successfully')), 'info');
|
||||
this.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, result.error || _('Failed to save settings')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
installRtty: function(ev) {
|
||||
ev.preventDefault();
|
||||
ui.showModal(_('Installing rtty'), [
|
||||
E('p', {}, _('Installing rtty-openssl package...')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
API.remoteInstall().then(L.bind(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, result.message || _('Installed successfully')), 'info');
|
||||
this.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, result.error || _('Installation failed')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
@ -219,6 +382,91 @@ return view.extend({
|
||||
}, this));
|
||||
},
|
||||
|
||||
// TTYD Web Console methods
|
||||
installTtyd: function(ev) {
|
||||
ev.preventDefault();
|
||||
ui.showModal(_('Installing Web Console'), [
|
||||
E('p', {}, _('Installing ttyd...')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
API.ttydInstall().then(L.bind(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('Web Console installed successfully')), 'info');
|
||||
this.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, result.error || _('Installation failed')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
startTtyd: function() {
|
||||
ui.showModal(_('Starting Web Console'), [
|
||||
E('p', {}, _('Starting ttyd service...')),
|
||||
E('div', { 'class': 'spinning' })
|
||||
]);
|
||||
API.ttydStart().then(L.bind(function(result) {
|
||||
ui.hideModal();
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('Web Console started on port ') + result.port), 'info');
|
||||
this.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, result.error || _('Failed to start')), 'error');
|
||||
}
|
||||
}, this)).catch(function(err) {
|
||||
ui.hideModal();
|
||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
stopTtyd: function() {
|
||||
API.ttydStop().then(L.bind(function(result) {
|
||||
if (result.success) {
|
||||
ui.addNotification(null, E('p', {}, _('Web Console stopped')), 'info');
|
||||
this.reload();
|
||||
} else {
|
||||
ui.addNotification(null, E('p', {}, result.error || _('Failed to stop')), 'error');
|
||||
}
|
||||
}, this));
|
||||
},
|
||||
|
||||
toggleTtyd: function() {
|
||||
if (this.ttyd && this.ttyd.running) {
|
||||
this.stopTtyd();
|
||||
} else {
|
||||
this.startTtyd();
|
||||
}
|
||||
},
|
||||
|
||||
openTtydFullscreen: function() {
|
||||
var port = (this.ttyd && this.ttyd.port) || 7681;
|
||||
window.open('http://' + window.location.hostname + ':' + port, '_blank');
|
||||
},
|
||||
|
||||
refreshTtydStatus: function() {
|
||||
API.ttydStatus().then(L.bind(function(status) {
|
||||
this.ttyd = status;
|
||||
var badge = document.getElementById('ttyd-status-badge');
|
||||
var text = document.getElementById('ttyd-status-text');
|
||||
var btn = document.getElementById('ttyd-toggle-btn');
|
||||
|
||||
if (badge) badge.textContent = status.running ? _('Running') : _('Stopped');
|
||||
if (text) {
|
||||
text.textContent = status.installed ? (status.running ? _('Running') : _('Stopped')) : _('Not Installed');
|
||||
text.style.color = status.running ? '#22c55e' : '#f59e0b';
|
||||
}
|
||||
if (btn) btn.innerHTML = status.running ? '⏹️ ' + _('Stop') : '▶️ ' + _('Start');
|
||||
|
||||
// Reload to update iframe visibility
|
||||
if (status.running !== this.ttyd.running) {
|
||||
this.reload();
|
||||
}
|
||||
}, this));
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
|
||||
@ -147,7 +147,7 @@ get_system_info() {
|
||||
|
||||
# NTP sync status
|
||||
local ntp_ok=0
|
||||
if [ -f /var/state/ntpd ] || pgrep -x ntpd >/dev/null 2>&1 || pgrep -x chronyd >/dev/null 2>&1; then
|
||||
if [ -f /var/state/ntpd ] || pidof ntpd >/dev/null 2>&1 || pidof chronyd >/dev/null 2>&1; then
|
||||
# Check if time seems reasonable (after year 2020)
|
||||
local year=$(date +%Y)
|
||||
[ "$year" -ge 2020 ] && ntp_ok=1
|
||||
@ -1225,41 +1225,54 @@ get_diagnostic_profile() {
|
||||
}
|
||||
|
||||
remote_status() {
|
||||
local section="system-hub.remote"
|
||||
local enabled=$(uci -q get $section.rustdesk_enabled || echo 0)
|
||||
local relay_server=$(uci -q get $section.rustdesk_server || echo "")
|
||||
local relay_key=$(uci -q get $section.rustdesk_key || echo "")
|
||||
local stored_id=$(uci -q get $section.rustdesk_id || echo "")
|
||||
local stored_password=$(uci -q get $section.rustdesk_password || echo "")
|
||||
local allow_unattended=$(uci -q get $section.allow_unattended || echo 0)
|
||||
local require_approval=$(uci -q get $section.require_approval || echo 1)
|
||||
local notify_on_connect=$(uci -q get $section.notify_on_connect || echo 1)
|
||||
|
||||
local installed=0
|
||||
command -v rustdesk >/dev/null 2>&1 && installed=1
|
||||
|
||||
local running=0
|
||||
if [ -x /etc/init.d/rustdesk ]; then
|
||||
/etc/init.d/rustdesk status >/dev/null 2>&1 && running=1
|
||||
local enabled=0
|
||||
|
||||
# Check if rtty is installed
|
||||
command -v rtty >/dev/null 2>&1 && installed=1
|
||||
|
||||
# Check if running
|
||||
if pidof rtty >/dev/null 2>&1; then
|
||||
running=1
|
||||
fi
|
||||
|
||||
# Check if enabled
|
||||
if [ -x /etc/init.d/rtty ]; then
|
||||
[ -f /etc/rc.d/S*rtty ] && enabled=1
|
||||
fi
|
||||
|
||||
# Get config from UCI
|
||||
local host=$(uci -q get rtty.@rtty[0].host || echo "")
|
||||
local port=$(uci -q get rtty.@rtty[0].port || echo "5912")
|
||||
local device_id=$(uci -q get rtty.@rtty[0].id || echo "")
|
||||
local description=$(uci -q get rtty.@rtty[0].description || echo "")
|
||||
local ssl=$(uci -q get rtty.@rtty[0].ssl || echo "0")
|
||||
local token=$(uci -q get rtty.@rtty[0].token || echo "")
|
||||
|
||||
# Auto-generate ID from MAC if not set
|
||||
if [ -z "$device_id" ]; then
|
||||
local iface=$(uci -q get rtty.@rtty[0].interface || echo "lan")
|
||||
device_id=$(cat /sys/class/net/br-$iface/address 2>/dev/null | tr -d ':' || cat /sys/class/net/eth0/address 2>/dev/null | tr -d ':' || echo "")
|
||||
fi
|
||||
|
||||
json_init
|
||||
json_add_boolean "installed" "$installed"
|
||||
json_add_boolean "running" "$running"
|
||||
json_add_boolean "enabled" "$enabled"
|
||||
json_add_string "server" "$relay_server"
|
||||
json_add_string "key" "$relay_key"
|
||||
json_add_string "id" "$stored_id"
|
||||
json_add_string "password" "$stored_password"
|
||||
json_add_boolean "allow_unattended" "$allow_unattended"
|
||||
json_add_boolean "require_approval" "$require_approval"
|
||||
json_add_boolean "notify_on_connect" "$notify_on_connect"
|
||||
json_add_string "host" "$host"
|
||||
json_add_int "port" "$port"
|
||||
json_add_string "id" "$device_id"
|
||||
json_add_string "description" "$description"
|
||||
json_add_boolean "ssl" "$ssl"
|
||||
json_add_string "token" "$token"
|
||||
json_dump
|
||||
}
|
||||
|
||||
remote_install() {
|
||||
json_init
|
||||
if command -v rustdesk >/dev/null 2>&1; then
|
||||
|
||||
if command -v rtty >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "already_installed"
|
||||
json_dump
|
||||
@ -1273,12 +1286,12 @@ remote_install() {
|
||||
return
|
||||
fi
|
||||
|
||||
opkg update >/tmp/rustdesk-install.log 2>&1
|
||||
if opkg install rustdesk >>/tmp/rustdesk-install.log 2>&1; then
|
||||
opkg update >/tmp/rtty-install.log 2>&1
|
||||
if opkg install rtty-openssl >>/tmp/rtty-install.log 2>&1; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "RustDesk installed"
|
||||
json_add_string "message" "rtty installed successfully"
|
||||
else
|
||||
local err="$(tail -n 20 /tmp/rustdesk-install.log 2>/dev/null)"
|
||||
local err="$(tail -n 20 /tmp/rtty-install.log 2>/dev/null)"
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "${err:-install_failed}"
|
||||
fi
|
||||
@ -1288,36 +1301,28 @@ remote_install() {
|
||||
remote_configure() {
|
||||
read input
|
||||
json_load "$input"
|
||||
local section="system-hub.remote"
|
||||
local server key enabled
|
||||
json_get_var server relay_server
|
||||
json_get_var key relay_key
|
||||
json_get_var enabled rustdesk_enabled
|
||||
|
||||
[ -n "$server" ] && uci set $section.rustdesk_server="$server"
|
||||
[ -n "$key" ] && uci set $section.rustdesk_key="$key"
|
||||
[ -n "$enabled" ] && uci set $section.rustdesk_enabled="$enabled"
|
||||
uci commit system-hub
|
||||
local host port device_id description ssl token
|
||||
json_get_var host host
|
||||
json_get_var port port
|
||||
json_get_var device_id id
|
||||
json_get_var description description
|
||||
json_get_var ssl ssl
|
||||
json_get_var token token
|
||||
|
||||
if [ -n "$server" ] || [ -n "$key" ]; then
|
||||
mkdir -p /etc/rustdesk
|
||||
cat > /etc/rustdesk/config.toml <<EOF
|
||||
[relay]
|
||||
server = "${server:-$(uci -q get $section.rustdesk_server)}"
|
||||
|
||||
[options]
|
||||
key = "${key:-$(uci -q get $section.rustdesk_key)}"
|
||||
auto_start = true
|
||||
EOF
|
||||
# Ensure rtty config section exists
|
||||
if ! uci -q get rtty.@rtty[0] >/dev/null 2>&1; then
|
||||
uci add rtty rtty
|
||||
fi
|
||||
|
||||
if [ -x /etc/init.d/rustdesk ] && [ "${enabled:-0}" = "1" ]; then
|
||||
/etc/init.d/rustdesk enable >/dev/null 2>&1 || true
|
||||
/etc/init.d/rustdesk restart >/dev/null 2>&1 || true
|
||||
elif [ -x /etc/init.d/rustdesk ]; then
|
||||
/etc/init.d/rustdesk stop >/dev/null 2>&1 || true
|
||||
/etc/init.d/rustdesk disable >/dev/null 2>&1 || true
|
||||
fi
|
||||
[ -n "$host" ] && uci set rtty.@rtty[0].host="$host"
|
||||
[ -n "$port" ] && uci set rtty.@rtty[0].port="$port"
|
||||
[ -n "$device_id" ] && uci set rtty.@rtty[0].id="$device_id"
|
||||
[ -n "$description" ] && uci set rtty.@rtty[0].description="$description"
|
||||
[ -n "$ssl" ] && uci set rtty.@rtty[0].ssl="$ssl"
|
||||
[ -n "$token" ] && uci set rtty.@rtty[0].token="$token"
|
||||
|
||||
uci commit rtty
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
@ -1325,19 +1330,19 @@ EOF
|
||||
}
|
||||
|
||||
remote_get_credentials() {
|
||||
local section="system-hub.remote"
|
||||
local rid="" rpass=""
|
||||
if command -v rustdesk >/dev/null 2>&1; then
|
||||
rid=$(rustdesk --get-id 2>/dev/null || echo "")
|
||||
rpass=$(rustdesk --password 2>/dev/null || echo "")
|
||||
local device_id=$(uci -q get rtty.@rtty[0].id || echo "")
|
||||
local token=$(uci -q get rtty.@rtty[0].token || echo "")
|
||||
|
||||
# Auto-generate ID from MAC if not set
|
||||
if [ -z "$device_id" ]; then
|
||||
local iface=$(uci -q get rtty.@rtty[0].interface || echo "lan")
|
||||
device_id=$(cat /sys/class/net/br-$iface/address 2>/dev/null | tr -d ':' || cat /sys/class/net/eth0/address 2>/dev/null | tr -d ':' || echo "")
|
||||
fi
|
||||
[ -z "$rid" ] && rid=$(uci -q get $section.rustdesk_id || echo "")
|
||||
[ -z "$rpass" ] && rpass=$(uci -q get $section.rustdesk_password || echo "")
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "id" "$rid"
|
||||
json_add_string "password" "$rpass"
|
||||
json_add_string "id" "$device_id"
|
||||
json_add_string "token" "$token"
|
||||
json_dump
|
||||
}
|
||||
|
||||
@ -1347,21 +1352,87 @@ remote_service_action() {
|
||||
json_get_var action action
|
||||
|
||||
json_init
|
||||
if [ ! -x /etc/init.d/rustdesk ]; then
|
||||
|
||||
if ! command -v rtty >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "service_missing"
|
||||
json_add_string "error" "not_installed"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
case "$action" in
|
||||
start|stop|restart|enable|disable)
|
||||
if /etc/init.d/rustdesk "$action" >/dev/null 2>&1; then
|
||||
start)
|
||||
# Stop any existing instance
|
||||
killall rtty 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Get config
|
||||
local host=$(uci -q get rtty.@rtty[0].host)
|
||||
local port=$(uci -q get rtty.@rtty[0].port || echo "5912")
|
||||
local device_id=$(uci -q get rtty.@rtty[0].id)
|
||||
local token=$(uci -q get rtty.@rtty[0].token)
|
||||
local ssl=$(uci -q get rtty.@rtty[0].ssl || echo "0")
|
||||
local description=$(uci -q get rtty.@rtty[0].description)
|
||||
|
||||
if [ -z "$host" ]; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "no_server_configured"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Build command
|
||||
local cmd="rtty -h $host -p $port -a -D"
|
||||
[ -n "$device_id" ] && cmd="$cmd -I $device_id"
|
||||
[ -n "$token" ] && cmd="$cmd -t $token"
|
||||
[ -n "$description" ] && cmd="$cmd -d \"$description\""
|
||||
[ "$ssl" = "1" ] && cmd="$cmd -s -x"
|
||||
|
||||
eval $cmd
|
||||
|
||||
sleep 2
|
||||
if pidof rtty >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "$action"
|
||||
json_add_string "message" "rtty started"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "action_failed"
|
||||
json_add_string "error" "failed_to_start"
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if killall rtty 2>/dev/null; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "rtty stopped"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "not_running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
killall rtty 2>/dev/null || true
|
||||
sleep 1
|
||||
# Recursive call to start
|
||||
echo '{"action":"start"}' | remote_service_action
|
||||
return
|
||||
;;
|
||||
enable)
|
||||
if [ -x /etc/init.d/rtty ]; then
|
||||
/etc/init.d/rtty enable >/dev/null 2>&1
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "rtty enabled"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "init_script_missing"
|
||||
fi
|
||||
;;
|
||||
disable)
|
||||
if [ -x /etc/init.d/rtty ]; then
|
||||
/etc/init.d/rtty disable >/dev/null 2>&1
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "rtty disabled"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "init_script_missing"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
@ -1375,16 +1446,195 @@ remote_service_action() {
|
||||
remote_save_settings() {
|
||||
read input
|
||||
json_load "$input"
|
||||
local section="system-hub.remote"
|
||||
local allow require notify
|
||||
json_get_var allow allow_unattended
|
||||
json_get_var require require_approval
|
||||
json_get_var notify notify_on_connect
|
||||
|
||||
[ -n "$allow" ] && uci set $section.allow_unattended="$allow"
|
||||
[ -n "$require" ] && uci set $section.require_approval="$require"
|
||||
[ -n "$notify" ] && uci set $section.notify_on_connect="$notify"
|
||||
uci commit system-hub
|
||||
local host port device_id description ssl token
|
||||
json_get_var host host
|
||||
json_get_var port port
|
||||
json_get_var device_id id
|
||||
json_get_var description description
|
||||
json_get_var ssl ssl
|
||||
json_get_var token token
|
||||
|
||||
# Ensure rtty config section exists
|
||||
if ! uci -q get rtty.@rtty[0] >/dev/null 2>&1; then
|
||||
uci add rtty rtty
|
||||
fi
|
||||
|
||||
[ -n "$host" ] && uci set rtty.@rtty[0].host="$host"
|
||||
[ -n "$port" ] && uci set rtty.@rtty[0].port="$port"
|
||||
[ -n "$device_id" ] && uci set rtty.@rtty[0].id="$device_id"
|
||||
[ -n "$description" ] && uci set rtty.@rtty[0].description="$description"
|
||||
[ -n "$ssl" ] && uci set rtty.@rtty[0].ssl="$ssl"
|
||||
[ -n "$token" ] && uci set rtty.@rtty[0].token="$token"
|
||||
|
||||
uci commit rtty
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# TTYD Web Console Functions
|
||||
# ============================================
|
||||
|
||||
ttyd_status() {
|
||||
local installed=0
|
||||
local running=0
|
||||
local enabled=0
|
||||
local port=7681
|
||||
local interface="lan"
|
||||
|
||||
# Check if installed
|
||||
command -v ttyd >/dev/null 2>&1 && installed=1
|
||||
|
||||
# Check if running
|
||||
if pidof ttyd >/dev/null 2>&1; then
|
||||
running=1
|
||||
fi
|
||||
|
||||
# Check if enabled in init
|
||||
if [ -x /etc/init.d/ttyd ] && [ -f /etc/rc.d/S*ttyd 2>/dev/null ]; then
|
||||
enabled=1
|
||||
fi
|
||||
|
||||
# Get port from UCI config
|
||||
local uci_port=$(uci -q get ttyd.@ttyd[0].port)
|
||||
[ -n "$uci_port" ] && port="$uci_port"
|
||||
|
||||
# Get interface binding
|
||||
local uci_interface=$(uci -q get ttyd.@ttyd[0].interface)
|
||||
[ -n "$uci_interface" ] && interface="$uci_interface"
|
||||
|
||||
json_init
|
||||
json_add_boolean "installed" "$installed"
|
||||
json_add_boolean "running" "$running"
|
||||
json_add_boolean "enabled" "$enabled"
|
||||
json_add_int "port" "$port"
|
||||
json_add_string "interface" "$interface"
|
||||
json_dump
|
||||
}
|
||||
|
||||
ttyd_install() {
|
||||
json_init
|
||||
|
||||
if command -v ttyd >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "already_installed"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
if ! command -v opkg >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "opkg_missing"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
opkg update >/tmp/ttyd-install.log 2>&1
|
||||
if opkg install ttyd >>/tmp/ttyd-install.log 2>&1; then
|
||||
# Configure ttyd for LAN only by default
|
||||
if [ ! -f /etc/config/ttyd ]; then
|
||||
cat > /etc/config/ttyd <<'TTYDCONF'
|
||||
config ttyd
|
||||
option port '7681'
|
||||
option interface 'lan'
|
||||
option command '/bin/login'
|
||||
option ipv6 '0'
|
||||
TTYDCONF
|
||||
fi
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "ttyd installed successfully"
|
||||
else
|
||||
local err="$(tail -n 20 /tmp/ttyd-install.log 2>/dev/null)"
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "${err:-install_failed}"
|
||||
fi
|
||||
json_dump
|
||||
}
|
||||
|
||||
ttyd_start() {
|
||||
json_init
|
||||
|
||||
if ! command -v ttyd >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "not_installed"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Stop any existing instance
|
||||
killall ttyd 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Get config
|
||||
local port=$(uci -q get ttyd.@ttyd[0].port || echo 7681)
|
||||
local interface=$(uci -q get ttyd.@ttyd[0].interface || echo "lan")
|
||||
|
||||
# Get interface IP
|
||||
local bind_ip=""
|
||||
if [ "$interface" != "" ] && [ "$interface" != "0.0.0.0" ]; then
|
||||
bind_ip=$(ubus call network.interface.$interface status 2>/dev/null | jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Start ttyd
|
||||
if [ -n "$bind_ip" ]; then
|
||||
ttyd -p "$port" -i "$bind_ip" -W /bin/login &
|
||||
else
|
||||
ttyd -p "$port" -W /bin/login &
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
|
||||
if pidof ttyd >/dev/null 2>&1; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "ttyd started on port $port"
|
||||
json_add_int "port" "$port"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "failed_to_start"
|
||||
fi
|
||||
json_dump
|
||||
}
|
||||
|
||||
ttyd_stop() {
|
||||
json_init
|
||||
|
||||
if killall ttyd 2>/dev/null; then
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "ttyd stopped"
|
||||
else
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "not_running"
|
||||
fi
|
||||
json_dump
|
||||
}
|
||||
|
||||
ttyd_configure() {
|
||||
read input
|
||||
json_load "$input"
|
||||
|
||||
local port interface
|
||||
json_get_var port port
|
||||
json_get_var interface interface
|
||||
|
||||
# Ensure config exists
|
||||
if [ ! -f /etc/config/ttyd ]; then
|
||||
touch /etc/config/ttyd
|
||||
uci set ttyd.main=ttyd
|
||||
fi
|
||||
|
||||
[ -n "$port" ] && uci set ttyd.@ttyd[0].port="$port"
|
||||
[ -n "$interface" ] && uci set ttyd.@ttyd[0].interface="$interface"
|
||||
uci commit ttyd
|
||||
|
||||
# Restart if running
|
||||
if pidof ttyd >/dev/null 2>&1; then
|
||||
killall ttyd 2>/dev/null
|
||||
sleep 1
|
||||
ttyd_start >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
@ -1634,7 +1884,7 @@ is_service_running() {
|
||||
fi
|
||||
|
||||
# Fallback: check process
|
||||
pgrep -x "$svc" >/dev/null 2>&1 && return 0
|
||||
pidof "$svc" >/dev/null 2>&1 && return 0
|
||||
|
||||
return 1
|
||||
}
|
||||
@ -1906,7 +2156,12 @@ case "$1" in
|
||||
"allow_unattended": 0,
|
||||
"require_approval": 1,
|
||||
"notify_on_connect": 1
|
||||
}
|
||||
},
|
||||
"ttyd_status": {},
|
||||
"ttyd_install": {},
|
||||
"ttyd_start": {},
|
||||
"ttyd_stop": {},
|
||||
"ttyd_configure": { "port": 7681, "interface": "lan" }
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
@ -1942,6 +2197,11 @@ EOF
|
||||
remote_get_credentials) remote_get_credentials ;;
|
||||
remote_service_action) remote_service_action ;;
|
||||
remote_save_settings) remote_save_settings ;;
|
||||
ttyd_status) ttyd_status ;;
|
||||
ttyd_install) ttyd_install ;;
|
||||
ttyd_start) ttyd_start ;;
|
||||
ttyd_stop) ttyd_stop ;;
|
||||
ttyd_configure) ttyd_configure ;;
|
||||
*)
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
"download_diagnostic",
|
||||
"run_diagnostic_test",
|
||||
"remote_status",
|
||||
"remote_get_credentials"
|
||||
"remote_get_credentials",
|
||||
"ttyd_status"
|
||||
],
|
||||
"luci.secubox": [
|
||||
"modules",
|
||||
@ -48,7 +49,11 @@
|
||||
"remote_install",
|
||||
"remote_configure",
|
||||
"remote_service_action",
|
||||
"remote_save_settings"
|
||||
"remote_save_settings",
|
||||
"ttyd_install",
|
||||
"ttyd_start",
|
||||
"ttyd_stop",
|
||||
"ttyd_configure"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user