secubox-openwrt/package/secubox/luci-app-system-hub/htdocs/luci-static/resources/view/system-hub/remote.js
CyberMind-FR 66aa12d6b6 feat: Add SecuBox portal header to all System Hub views
Add unified SecuBox header navigation to all 10 System Hub views
for consistent portal integration when accessed from SecuBox Portal:
- overview.js, health.js, services.js, diagnostics.js
- logs.js, backup.js, components.js, settings.js
- dev-status.js, remote.js

Pattern: Wrap view content with secubox-page-wrapper and prepend
SbHeader.render() to hide LuCI sidebar when in portal context.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:46:48 +01:00

478 lines
17 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
'require view';
'require dom';
'require ui';
'require secubox-theme/theme as Theme';
'require system-hub/api as API';
'require system-hub/theme-assets as ThemeAssets';
'require system-hub/nav as HubNav';
'require secubox-portal/header as SbHeader';
var shLang = (typeof L !== 'undefined' && L.env && L.env.lang) ||
(document.documentElement && document.documentElement.getAttribute('lang')) ||
(navigator.language ? navigator.language.split('-')[0] : 'en');
Theme.init({ language: shLang });
return view.extend({
load: function() {
return Promise.all([
API.remoteStatus(),
API.ttydStatus()
]);
},
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'),
// 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' }, '🔗'), _('rtty - Remote Terminal') ]),
E('div', { 'class': 'sh-card-badge', 'id': 'rtty-status-badge' }, remote.running ? _('Connected') : _('Disconnected'))
]),
E('div', { 'class': 'sh-card-body' }, [
// Device ID
E('div', { 'class': 'sh-remote-id' }, [
E('div', { 'class': 'sh-remote-id-icon' }, '🆔'),
E('div', {}, [
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'))
])
]),
// 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', {}, _('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 ? _('Connected to relay server') : _('Installed but not connected'))
]),
// Actions
E('div', { 'class': 'sh-btn-group', 'style': 'margin-top: 16px;' }, [
E('button', {
'class': 'sh-btn sh-btn-primary',
'id': 'rtty-save-btn',
'click': L.bind(this.saveRttySettings, this)
}, [ '💾 ', _('Save Settings') ]),
E('button', {
'class': 'sh-btn',
'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' }, '🔐'), _('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;' }, _('Active'))
]),
E('div', { 'class': 'sh-sysinfo-item' }, [
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;' }, 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-body' }, [
E('div', { 'class': 'sh-sysinfo-grid' }, [
E('div', { 'class': 'sh-sysinfo-item' }, [
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-value' }, 'support@cybermind.fr')
]),
E('div', { 'class': 'sh-sysinfo-item' }, [
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-value' }, 'https://cybermind.fr')
])
])
])
])
]);
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
wrapper.appendChild(SbHeader.render());
wrapper.appendChild(view);
return wrapper;
},
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', {}, [
E('div', { 'class': 'sh-toggle-label' }, label),
E('div', { 'class': 'sh-toggle-desc' }, desc)
])
]),
E('div', {
'class': 'sh-toggle-switch' + (enabled ? ' active' : ''),
'id': id,
'click': function(ev) {
ev.target.classList.toggle('active');
}
})
]);
},
showCredentials: function() {
ui.showModal(_('rtty Credentials'), [
E('p', {}, _('Retrieving credentials...')),
E('div', { 'class': 'spinning' })
]);
API.remoteCredentials().then(L.bind(function(result) {
ui.hideModal();
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 }, _('Close'))
])
]);
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
toggleRttyService: function() {
if (!this.remote || !this.remote.installed) return;
var action = this.remote.running ? 'stop' : 'start';
ui.showModal(action === 'start' ? _('Connecting...') : _('Disconnecting...'), [
E('p', {}, action === 'start' ? _('Connecting to relay server...') : _('Stopping rtty service...')),
E('div', { 'class': 'spinning' })
]);
API.remoteServiceAction(action).then(L.bind(function(res) {
ui.hideModal();
if (res.success) {
ui.addNotification(null, E('p', {}, '✅ ' + (res.message || action)), 'info');
this.reload();
} else {
ui.addNotification(null, E('p', {}, res.error || _('Action failed')), 'error');
}
}, this)).catch(function(err) {
ui.hideModal();
ui.addNotification(null, E('p', {}, err.message || err), 'error');
});
},
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({
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');
});
},
reload: function() {
this.load().then(L.bind(function(data) {
var node = this.render(data);
var root = document.querySelector('.system-hub-dashboard');
if (root && root.parentNode) {
root.parentNode.replaceChild(node, root);
}
}, 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
});