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
|
* System Hub API
|
||||||
* Package: luci-app-system-hub
|
* Package: luci-app-system-hub
|
||||||
* RPCD object: luci.system-hub
|
* RPCD object: luci.system-hub
|
||||||
* Version: 0.3.6
|
* Version: 0.4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Debug log to verify correct version is loaded
|
// 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({
|
var callStatus = rpc.declare({
|
||||||
object: 'luci.system-hub',
|
object: 'luci.system-hub',
|
||||||
@ -183,7 +183,7 @@ var callRemoteInstall = rpc.declare({
|
|||||||
var callRemoteConfigure = rpc.declare({
|
var callRemoteConfigure = rpc.declare({
|
||||||
object: 'luci.system-hub',
|
object: 'luci.system-hub',
|
||||||
method: 'remote_configure',
|
method: 'remote_configure',
|
||||||
params: ['relay_server', 'relay_key', 'rustdesk_enabled'],
|
params: ['host', 'port', 'id', 'description', 'ssl', 'token'],
|
||||||
expect: {}
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,7 +203,39 @@ var callRemoteServiceAction = rpc.declare({
|
|||||||
var callRemoteSaveSettings = rpc.declare({
|
var callRemoteSaveSettings = rpc.declare({
|
||||||
object: 'luci.system-hub',
|
object: 'luci.system-hub',
|
||||||
method: 'remote_save_settings',
|
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: {}
|
expect: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -287,5 +319,14 @@ return baseclass.extend({
|
|||||||
},
|
},
|
||||||
remoteSaveSettings: function(data) {
|
remoteSaveSettings: function(data) {
|
||||||
return callRemoteSaveSettings(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({
|
return view.extend({
|
||||||
load: function() {
|
load: function() {
|
||||||
return API.remoteStatus();
|
return Promise.all([
|
||||||
|
API.remoteStatus(),
|
||||||
|
API.ttydStatus()
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(remote) {
|
render: function(data) {
|
||||||
this.remote = remote || {};
|
var remote = data[0] || {};
|
||||||
|
var ttyd = data[1] || {};
|
||||||
|
this.remote = remote;
|
||||||
|
this.ttyd = ttyd;
|
||||||
|
|
||||||
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
var view = E('div', { 'class': 'system-hub-dashboard' }, [
|
||||||
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('secubox-theme/secubox-theme.css') }),
|
||||||
ThemeAssets.stylesheet('common.css'),
|
ThemeAssets.stylesheet('common.css'),
|
||||||
ThemeAssets.stylesheet('dashboard.css'),
|
ThemeAssets.stylesheet('dashboard.css'),
|
||||||
HubNav.renderTabs('remote'),
|
HubNav.renderTabs('remote'),
|
||||||
|
|
||||||
// RustDesk Section
|
// rtty Remote Access Section
|
||||||
E('div', { 'class': 'sh-card sh-remote-card' }, [
|
E('div', { 'class': 'sh-card sh-remote-card' }, [
|
||||||
E('div', { 'class': 'sh-card-header' }, [
|
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-title' }, [ E('span', { 'class': 'sh-card-title-icon' }, '🔗'), _('rtty - Remote Terminal') ]),
|
||||||
E('div', { 'class': 'sh-card-badge' }, remote.enabled ? 'Actif' : 'Inactif')
|
E('div', { 'class': 'sh-card-badge', 'id': 'rtty-status-badge' }, remote.running ? _('Connected') : _('Disconnected'))
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-card-body' }, [
|
E('div', { 'class': 'sh-card-body' }, [
|
||||||
// RustDesk ID
|
// Device ID
|
||||||
E('div', { 'class': 'sh-remote-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', {}, [
|
||||||
E('div', { 'class': 'sh-remote-id-value', 'id': 'remote-id-value' }, remote.id || '--- --- ---'),
|
E('div', { 'class': 'sh-remote-id-value', 'id': 'rtty-device-id' }, remote.id || _('Not configured')),
|
||||||
E('div', { 'class': 'sh-remote-id-label' }, 'ID RustDesk - Communiquez ce code au support')
|
E('div', { 'class': 'sh-remote-id-label' }, _('Device ID - Share this with support'))
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Settings
|
// Configuration fields
|
||||||
this.renderToggle('🔒', 'Accès sans surveillance', 'Permettre la connexion sans approbation', remote.allow_unattended, 'allow_unattended'),
|
E('div', { 'class': 'sh-form-grid', 'style': 'margin-top: 16px;' }, [
|
||||||
this.renderToggle('✅', 'Approbation requise', 'Confirmer chaque connexion entrante', remote.require_approval, 'require_approval'),
|
// Server Host
|
||||||
this.renderToggle('🔔', 'Notification de connexion', 'Recevoir une alerte à chaque session', remote.notify_on_connect, 'notify_on_connect'),
|
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
|
// 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;' }, [
|
!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', { 'style': 'font-size: 20px; margin-right: 12px;' }, '⚠️'),
|
||||||
E('span', {}, 'RustDesk n\'est pas installé. '),
|
E('span', {}, _('rtty is not installed.')),
|
||||||
E('a', { 'href': '#', 'style': 'color: #6366f1;', 'click': L.bind(this.installRustdesk, this) }, 'Installer maintenant')
|
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('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', { '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
|
// Actions
|
||||||
E('div', { 'class': 'sh-btn-group' }, [
|
E('div', { 'class': 'sh-btn-group', 'style': 'margin-top: 16px;' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'sh-btn sh-btn-primary',
|
'class': 'sh-btn sh-btn-primary',
|
||||||
'click': L.bind(this.showCredentials, this)
|
'id': 'rtty-save-btn',
|
||||||
}, [ '🔑 Identifiants' ]),
|
'click': L.bind(this.saveRttySettings, this)
|
||||||
E('button', {
|
}, [ '💾 ', _('Save Settings') ]),
|
||||||
|
E('button', {
|
||||||
'class': 'sh-btn',
|
'class': 'sh-btn',
|
||||||
'click': L.bind(this.toggleService, this)
|
'id': 'rtty-toggle-btn',
|
||||||
}, [ remote.running ? '⏹️ Arrêter' : '▶️ Démarrer' ])
|
'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
|
// SSH Section
|
||||||
E('div', { 'class': 'sh-card' }, [
|
E('div', { 'class': 'sh-card' }, [
|
||||||
E('div', { 'class': 'sh-card-header' }, [
|
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-card-body' }, [
|
||||||
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
||||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
E('div', { 'class': 'sh-sysinfo-item' }, [
|
||||||
E('span', { 'class': 'sh-sysinfo-label' }, 'Status'),
|
E('span', { 'class': 'sh-sysinfo-label' }, _('Status')),
|
||||||
E('span', { 'class': 'sh-sysinfo-value', 'style': 'color: #22c55e;' }, 'Actif')
|
E('span', { 'class': 'sh-sysinfo-value', 'style': 'color: #22c55e;' }, _('Active'))
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
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('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;' }, [
|
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)
|
// Support Contact (static)
|
||||||
E('div', { 'class': 'sh-card' }, [
|
E('div', { 'class': 'sh-card' }, [
|
||||||
E('div', { 'class': 'sh-card-header' }, [
|
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-card-body' }, [
|
||||||
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
E('div', { 'class': 'sh-sysinfo-grid' }, [
|
||||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
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('span', { 'class': 'sh-sysinfo-value' }, 'CyberMind.fr')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
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('span', { 'class': 'sh-sysinfo-value' }, 'support@cybermind.fr')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
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('span', { 'class': 'sh-sysinfo-value' }, '+33 1 23 45 67 89')
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'sh-sysinfo-item' }, [
|
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')
|
E('span', { 'class': 'sh-sysinfo-value' }, 'https://cybermind.fr')
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
@ -124,8 +247,8 @@ return view.extend({
|
|||||||
return view;
|
return view;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderToggle: function(icon, label, desc, enabled, field) {
|
renderToggle: function(icon, label, desc, enabled, id) {
|
||||||
return E('div', { 'class': 'sh-toggle' }, [
|
return E('div', { 'class': 'sh-toggle', 'style': 'margin-top: 16px;' }, [
|
||||||
E('div', { 'class': 'sh-toggle-info' }, [
|
E('div', { 'class': 'sh-toggle-info' }, [
|
||||||
E('span', { 'class': 'sh-toggle-icon' }, icon),
|
E('span', { 'class': 'sh-toggle-icon' }, icon),
|
||||||
E('div', {}, [
|
E('div', {}, [
|
||||||
@ -133,63 +256,58 @@ return view.extend({
|
|||||||
E('div', { 'class': 'sh-toggle-desc' }, desc)
|
E('div', { 'class': 'sh-toggle-desc' }, desc)
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('div', {
|
E('div', {
|
||||||
'class': 'sh-toggle-switch' + (enabled ? ' active' : ''),
|
'class': 'sh-toggle-switch' + (enabled ? ' active' : ''),
|
||||||
'data-field': field,
|
'id': id,
|
||||||
'click': L.bind(function(ev) {
|
'click': function(ev) {
|
||||||
ev.target.classList.toggle('active');
|
ev.target.classList.toggle('active');
|
||||||
this.saveSettings();
|
}
|
||||||
}, this)
|
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
showCredentials: function() {
|
showCredentials: function() {
|
||||||
ui.showModal(_('Identifiants RustDesk'), [
|
ui.showModal(_('rtty Credentials'), [
|
||||||
E('p', {}, 'Récupération en cours…'),
|
E('p', {}, _('Retrieving credentials...')),
|
||||||
E('div', { 'class': 'spinning' })
|
E('div', { 'class': 'spinning' })
|
||||||
]);
|
]);
|
||||||
API.remoteCredentials().then(function(result) {
|
API.remoteCredentials().then(L.bind(function(result) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
ui.showModal(_('Identifiants RustDesk'), [
|
ui.showModal(_('rtty Credentials'), [
|
||||||
E('div', { 'style': 'font-size:18px; margin-bottom:8px;' }, 'ID: ' + (result.id || '---')),
|
E('div', { 'style': 'font-size:16px; margin-bottom:12px;' }, [
|
||||||
E('div', { 'style': 'font-size:18px;' }, 'Mot de passe: ' + (result.password || '---')),
|
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('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.hideModal();
|
||||||
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
ui.addNotification(null, E('p', {}, err.message || err), 'error');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleService: function() {
|
toggleRttyService: function() {
|
||||||
if (!this.remote || !this.remote.installed) return;
|
if (!this.remote || !this.remote.installed) return;
|
||||||
var action = this.remote.running ? 'stop' : 'start';
|
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) {
|
ui.showModal(action === 'start' ? _('Connecting...') : _('Disconnecting...'), [
|
||||||
ev.preventDefault();
|
E('p', {}, action === 'start' ? _('Connecting to relay server...') : _('Stopping rtty service...')),
|
||||||
ui.showModal(_('Installation'), [
|
|
||||||
E('p', {}, 'Installation de RustDesk…'),
|
|
||||||
E('div', { 'class': 'spinning' })
|
E('div', { 'class': 'spinning' })
|
||||||
]);
|
]);
|
||||||
API.remoteInstall().then(L.bind(function(result) {
|
|
||||||
|
API.remoteServiceAction(action).then(L.bind(function(res) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
if (result.success) {
|
if (res.success) {
|
||||||
ui.addNotification(null, E('p', {}, result.message || 'Installé'), 'info');
|
ui.addNotification(null, E('p', {}, '✅ ' + (res.message || action)), 'info');
|
||||||
this.reload();
|
this.reload();
|
||||||
} else {
|
} 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) {
|
}, this)).catch(function(err) {
|
||||||
ui.hideModal();
|
ui.hideModal();
|
||||||
@ -197,15 +315,60 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
saveSettings: function() {
|
saveRttySettings: function() {
|
||||||
var allow = document.querySelector('[data-field="allow_unattended"]').classList.contains('active') ? 1 : 0;
|
var host = document.getElementById('rtty-host').value;
|
||||||
var require = document.querySelector('[data-field="require_approval"]').classList.contains('active') ? 1 : 0;
|
var port = parseInt(document.getElementById('rtty-port').value) || 5912;
|
||||||
var notify = document.querySelector('[data-field="notify_on_connect"]').classList.contains('active') ? 1 : 0;
|
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({
|
API.remoteSaveSettings({
|
||||||
allow_unattended: allow,
|
host: host,
|
||||||
require_approval: require,
|
port: port,
|
||||||
notify_on_connect: notify
|
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));
|
}, 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,
|
handleSaveApply: null,
|
||||||
handleSave: null,
|
handleSave: null,
|
||||||
handleReset: null
|
handleReset: null
|
||||||
|
|||||||
@ -147,7 +147,7 @@ get_system_info() {
|
|||||||
|
|
||||||
# NTP sync status
|
# NTP sync status
|
||||||
local ntp_ok=0
|
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)
|
# Check if time seems reasonable (after year 2020)
|
||||||
local year=$(date +%Y)
|
local year=$(date +%Y)
|
||||||
[ "$year" -ge 2020 ] && ntp_ok=1
|
[ "$year" -ge 2020 ] && ntp_ok=1
|
||||||
@ -1225,41 +1225,54 @@ get_diagnostic_profile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remote_status() {
|
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
|
local installed=0
|
||||||
command -v rustdesk >/dev/null 2>&1 && installed=1
|
|
||||||
|
|
||||||
local running=0
|
local running=0
|
||||||
if [ -x /etc/init.d/rustdesk ]; then
|
local enabled=0
|
||||||
/etc/init.d/rustdesk status >/dev/null 2>&1 && running=1
|
|
||||||
|
# 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
|
fi
|
||||||
|
|
||||||
json_init
|
json_init
|
||||||
json_add_boolean "installed" "$installed"
|
json_add_boolean "installed" "$installed"
|
||||||
json_add_boolean "running" "$running"
|
json_add_boolean "running" "$running"
|
||||||
json_add_boolean "enabled" "$enabled"
|
json_add_boolean "enabled" "$enabled"
|
||||||
json_add_string "server" "$relay_server"
|
json_add_string "host" "$host"
|
||||||
json_add_string "key" "$relay_key"
|
json_add_int "port" "$port"
|
||||||
json_add_string "id" "$stored_id"
|
json_add_string "id" "$device_id"
|
||||||
json_add_string "password" "$stored_password"
|
json_add_string "description" "$description"
|
||||||
json_add_boolean "allow_unattended" "$allow_unattended"
|
json_add_boolean "ssl" "$ssl"
|
||||||
json_add_boolean "require_approval" "$require_approval"
|
json_add_string "token" "$token"
|
||||||
json_add_boolean "notify_on_connect" "$notify_on_connect"
|
|
||||||
json_dump
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
remote_install() {
|
remote_install() {
|
||||||
json_init
|
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_boolean "success" 0
|
||||||
json_add_string "error" "already_installed"
|
json_add_string "error" "already_installed"
|
||||||
json_dump
|
json_dump
|
||||||
@ -1273,12 +1286,12 @@ remote_install() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
opkg update >/tmp/rustdesk-install.log 2>&1
|
opkg update >/tmp/rtty-install.log 2>&1
|
||||||
if opkg install rustdesk >>/tmp/rustdesk-install.log 2>&1; then
|
if opkg install rtty-openssl >>/tmp/rtty-install.log 2>&1; then
|
||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
json_add_string "message" "RustDesk installed"
|
json_add_string "message" "rtty installed successfully"
|
||||||
else
|
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_boolean "success" 0
|
||||||
json_add_string "error" "${err:-install_failed}"
|
json_add_string "error" "${err:-install_failed}"
|
||||||
fi
|
fi
|
||||||
@ -1288,36 +1301,28 @@ remote_install() {
|
|||||||
remote_configure() {
|
remote_configure() {
|
||||||
read input
|
read input
|
||||||
json_load "$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"
|
local host port device_id description ssl token
|
||||||
[ -n "$key" ] && uci set $section.rustdesk_key="$key"
|
json_get_var host host
|
||||||
[ -n "$enabled" ] && uci set $section.rustdesk_enabled="$enabled"
|
json_get_var port port
|
||||||
uci commit system-hub
|
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
|
# Ensure rtty config section exists
|
||||||
mkdir -p /etc/rustdesk
|
if ! uci -q get rtty.@rtty[0] >/dev/null 2>&1; then
|
||||||
cat > /etc/rustdesk/config.toml <<EOF
|
uci add rtty rtty
|
||||||
[relay]
|
|
||||||
server = "${server:-$(uci -q get $section.rustdesk_server)}"
|
|
||||||
|
|
||||||
[options]
|
|
||||||
key = "${key:-$(uci -q get $section.rustdesk_key)}"
|
|
||||||
auto_start = true
|
|
||||||
EOF
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -x /etc/init.d/rustdesk ] && [ "${enabled:-0}" = "1" ]; then
|
[ -n "$host" ] && uci set rtty.@rtty[0].host="$host"
|
||||||
/etc/init.d/rustdesk enable >/dev/null 2>&1 || true
|
[ -n "$port" ] && uci set rtty.@rtty[0].port="$port"
|
||||||
/etc/init.d/rustdesk restart >/dev/null 2>&1 || true
|
[ -n "$device_id" ] && uci set rtty.@rtty[0].id="$device_id"
|
||||||
elif [ -x /etc/init.d/rustdesk ]; then
|
[ -n "$description" ] && uci set rtty.@rtty[0].description="$description"
|
||||||
/etc/init.d/rustdesk stop >/dev/null 2>&1 || true
|
[ -n "$ssl" ] && uci set rtty.@rtty[0].ssl="$ssl"
|
||||||
/etc/init.d/rustdesk disable >/dev/null 2>&1 || true
|
[ -n "$token" ] && uci set rtty.@rtty[0].token="$token"
|
||||||
fi
|
|
||||||
|
uci commit rtty
|
||||||
|
|
||||||
json_init
|
json_init
|
||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
@ -1325,19 +1330,19 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
remote_get_credentials() {
|
remote_get_credentials() {
|
||||||
local section="system-hub.remote"
|
local device_id=$(uci -q get rtty.@rtty[0].id || echo "")
|
||||||
local rid="" rpass=""
|
local token=$(uci -q get rtty.@rtty[0].token || echo "")
|
||||||
if command -v rustdesk >/dev/null 2>&1; then
|
|
||||||
rid=$(rustdesk --get-id 2>/dev/null || echo "")
|
# Auto-generate ID from MAC if not set
|
||||||
rpass=$(rustdesk --password 2>/dev/null || echo "")
|
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
|
fi
|
||||||
[ -z "$rid" ] && rid=$(uci -q get $section.rustdesk_id || echo "")
|
|
||||||
[ -z "$rpass" ] && rpass=$(uci -q get $section.rustdesk_password || echo "")
|
|
||||||
|
|
||||||
json_init
|
json_init
|
||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
json_add_string "id" "$rid"
|
json_add_string "id" "$device_id"
|
||||||
json_add_string "password" "$rpass"
|
json_add_string "token" "$token"
|
||||||
json_dump
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1347,21 +1352,87 @@ remote_service_action() {
|
|||||||
json_get_var action action
|
json_get_var action action
|
||||||
|
|
||||||
json_init
|
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_boolean "success" 0
|
||||||
json_add_string "error" "service_missing"
|
json_add_string "error" "not_installed"
|
||||||
json_dump
|
json_dump
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$action" in
|
case "$action" in
|
||||||
start|stop|restart|enable|disable)
|
start)
|
||||||
if /etc/init.d/rustdesk "$action" >/dev/null 2>&1; then
|
# 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_boolean "success" 1
|
||||||
json_add_string "message" "$action"
|
json_add_string "message" "rtty started"
|
||||||
else
|
else
|
||||||
json_add_boolean "success" 0
|
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
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@ -1375,16 +1446,195 @@ remote_service_action() {
|
|||||||
remote_save_settings() {
|
remote_save_settings() {
|
||||||
read input
|
read input
|
||||||
json_load "$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"
|
local host port device_id description ssl token
|
||||||
[ -n "$require" ] && uci set $section.require_approval="$require"
|
json_get_var host host
|
||||||
[ -n "$notify" ] && uci set $section.notify_on_connect="$notify"
|
json_get_var port port
|
||||||
uci commit system-hub
|
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_init
|
||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
@ -1634,7 +1884,7 @@ is_service_running() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback: check process
|
# Fallback: check process
|
||||||
pgrep -x "$svc" >/dev/null 2>&1 && return 0
|
pidof "$svc" >/dev/null 2>&1 && return 0
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -1906,7 +2156,12 @@ case "$1" in
|
|||||||
"allow_unattended": 0,
|
"allow_unattended": 0,
|
||||||
"require_approval": 1,
|
"require_approval": 1,
|
||||||
"notify_on_connect": 1
|
"notify_on_connect": 1
|
||||||
}
|
},
|
||||||
|
"ttyd_status": {},
|
||||||
|
"ttyd_install": {},
|
||||||
|
"ttyd_start": {},
|
||||||
|
"ttyd_stop": {},
|
||||||
|
"ttyd_configure": { "port": 7681, "interface": "lan" }
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
;;
|
;;
|
||||||
@ -1942,6 +2197,11 @@ EOF
|
|||||||
remote_get_credentials) remote_get_credentials ;;
|
remote_get_credentials) remote_get_credentials ;;
|
||||||
remote_service_action) remote_service_action ;;
|
remote_service_action) remote_service_action ;;
|
||||||
remote_save_settings) remote_save_settings ;;
|
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_init
|
||||||
json_add_boolean "success" 0
|
json_add_boolean "success" 0
|
||||||
|
|||||||
@ -20,7 +20,8 @@
|
|||||||
"download_diagnostic",
|
"download_diagnostic",
|
||||||
"run_diagnostic_test",
|
"run_diagnostic_test",
|
||||||
"remote_status",
|
"remote_status",
|
||||||
"remote_get_credentials"
|
"remote_get_credentials",
|
||||||
|
"ttyd_status"
|
||||||
],
|
],
|
||||||
"luci.secubox": [
|
"luci.secubox": [
|
||||||
"modules",
|
"modules",
|
||||||
@ -48,7 +49,11 @@
|
|||||||
"remote_install",
|
"remote_install",
|
||||||
"remote_configure",
|
"remote_configure",
|
||||||
"remote_service_action",
|
"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