vnc embedded option

This commit is contained in:
Yuri Karamian
2025-12-18 23:41:18 +01:00
parent 9c09719fb9
commit f2f2222df7
11 changed files with 192 additions and 19 deletions
+4
View File
@@ -55,6 +55,7 @@ The module uses two primary configurable options that are dynamically populated
|--------|------------|-------------|
| **Plan ID** | `configoption1` | VPS plan - dropdown populated from `/api/v1/plans` showing plan names with prices |
| **OS ID** | `configoption2` | Operating system - dropdown populated from `/api/v1/os/plan/{plan_id}` based on selected plan |
| **VNC Display Mode** | `configoption3` | How VNC console is displayed: `newtab` (opens in new browser tab) or `embedded` (shows in a tab within the client area) |
#### Setting Up Configurable Options
@@ -176,6 +177,9 @@ Override translations by creating files in:
## Changelog
### Version 1.5
- WHMCS v9 compatibility: Updated Smarty template syntax for Smarty v4
### Version 1.4
- Fixed firewall rule duplication bug when adding new rules
- Added validation warning for ANY protocol with specific port numbers
+41 -9
View File
@@ -3,7 +3,7 @@
* WHMCS Server Module - VPSAG
*
* @package WHMCS
* @version 1.4
* @version 1.5
* @copyright Copyright (c) ArkHost 2025
* @author ArkHost <support@arkhost.com>
* @link https://arkhost.com
@@ -383,13 +383,29 @@ function ArkhostVPSAG_MetaData() {
'order' => 30,
'icon' => 'fa-sync'
]);
$actionsPanel->addChild('VPSVNC', [
'label' => $_LANG['VNC'],
'uri' => 'clientarea.php?action=productdetails&id=' . $serviceId . '&modop=custom&a=VNC',
'order' => 40,
'icon' => 'fa-desktop',
'attributes' => ['target' => '_blank']
]);
// Check VNC mode from product config
$product = Capsule::table('tblproducts')->where('id', $service->packageid)->first();
$vncMode = isset($product->configoption3) && $product->configoption3 !== '' ? $product->configoption3 : 'newtab';
if ($vncMode === 'embedded') {
// For embedded mode, link to the page with hash to trigger VNC tab
$actionsPanel->addChild('VPSVNC', [
'label' => $_LANG['VNC'],
'uri' => 'clientarea.php?action=productdetails&id=' . $serviceId . '#vnc',
'order' => 40,
'icon' => 'fa-desktop'
]);
} else {
// For new tab mode, open VNC in new window
$actionsPanel->addChild('VPSVNC', [
'label' => $_LANG['VNC'],
'uri' => 'clientarea.php?action=productdetails&id=' . $serviceId . '&modop=custom&a=VNC',
'order' => 40,
'icon' => 'fa-desktop',
'attributes' => ['target' => '_blank']
]);
}
});
}
@@ -422,6 +438,16 @@ function ArkhostVPSAG_ConfigOptions() {
'Type' => 'dropdown',
'Options' => array(),
),
'vnc_mode' => array(
'FriendlyName' => 'VNC Display Mode',
'Description' => 'How to display the VNC console to clients.',
'Type' => 'dropdown',
'Options' => array(
'newtab' => 'New Tab',
'embedded' => 'Embedded in Page',
),
'Default' => 'newtab',
),
// Extra resource options removed - now handled via plan selection
// Plans have fixed resources defined within them in the new API
);
@@ -718,7 +744,7 @@ function ArkhostVPSAG_AdminLink(array $params) {
function ArkhostVPSAG_ClientAreaAPI(array $params) {
try {
$action = App::getFromRequest('api');
$actions = array('Server Info', 'Graphs', 'Reinstall', 'Reboot', 'Stop', 'Start', 'IPv4 Addresses', 'Hostname rDNS', 'Create backup', 'Delete backup', 'List backups', 'Restore backup', 'Get Firewall rules', 'Add Firewall rules', 'Delete Firewall rule', 'Commit Firewall rules', 'ISO Images', 'Load ISO', 'Eject ISO', 'Reset root');
$actions = array('Server Info', 'Graphs', 'Reinstall', 'Reboot', 'Stop', 'Start', 'IPv4 Addresses', 'Hostname rDNS', 'Create backup', 'Delete backup', 'List backups', 'Restore backup', 'Get Firewall rules', 'Add Firewall rules', 'Delete Firewall rule', 'Commit Firewall rules', 'ISO Images', 'Load ISO', 'Eject ISO', 'Reset root', 'VNC Console');
$results = array('result' => 'success');
if (in_array($action, $actions)) {
@@ -1000,6 +1026,11 @@ function ArkhostVPSAG_ClientArea(array $params) {
$serverInfo['bandwidth_used'] = isset($serverInfo['bandwidth_used']) ? $serverInfo['bandwidth_used'] : 0;
$serverInfo['bandwidth'] = isset($serverInfo['bandwidth']) ? $serverInfo['bandwidth'] : 1;
// Get VNC mode setting (configoption3)
$vncMode = isset($params['configoption3']) && $params['configoption3'] !== ''
? $params['configoption3']
: 'newtab';
return array(
'templatefile' => 'template/clientarea_direct',
'templateVariables' => array(
@@ -1008,6 +1039,7 @@ function ArkhostVPSAG_ClientArea(array $params) {
'operatingSystems' => $operatingSystems,
'token' => generate_token('plain'),
'_LANG' => ArkhostVPSAG_Translation(),
'vncMode' => $vncMode,
)
);
} catch (Exception $err) {
+1 -1
View File
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Gebruikt';
$_ADDONLANG['Start'] = 'Start';
$_ADDONLANG['Stop'] = 'Stop';
$_ADDONLANG['Restart'] = 'Herstarten';
$_ADDONLANG['VNC'] = 'VNC-console';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Opmerking';
$_ADDONLANG['LoadingFirewallRules'] = 'Firewall-regels laden...';
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Used';
$_ADDONLANG['Start'] = 'Start';
$_ADDONLANG['Stop'] = 'Stop';
$_ADDONLANG['Restart'] = 'Restart';
$_ADDONLANG['VNC'] = 'VNC Console';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Note';
$_ADDONLANG['LoadingFirewallRules'] = 'Loading firewall rules...';
+1 -1
View File
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Utilisé';
$_ADDONLANG['Start'] = 'Démarrer';
$_ADDONLANG['Stop'] = 'Arrêter';
$_ADDONLANG['Restart'] = 'Redémarrer';
$_ADDONLANG['VNC'] = 'Console VNC';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Note';
$_ADDONLANG['LoadingFirewallRules'] = 'Chargement des règles de pare-feu...';
+1 -1
View File
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Verwendet';
$_ADDONLANG['Start'] = 'Starten';
$_ADDONLANG['Stop'] = 'Stoppen';
$_ADDONLANG['Restart'] = 'Neustart';
$_ADDONLANG['VNC'] = 'VNC-Konsole';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Notiz';
$_ADDONLANG['LoadingFirewallRules'] = 'Firewall-Regeln werden geladen...';
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Utilizzato';
$_ADDONLANG['Start'] = 'Avvia';
$_ADDONLANG['Stop'] = 'Ferma';
$_ADDONLANG['Restart'] = 'Riavvia';
$_ADDONLANG['VNC'] = 'Console VNC';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Nota';
$_ADDONLANG['LoadingFirewallRules'] = 'Caricamento regole firewall...';
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Utilizado';
$_ADDONLANG['Start'] = 'Iniciar';
$_ADDONLANG['Stop'] = 'Parar';
$_ADDONLANG['Restart'] = 'Reiniciar';
$_ADDONLANG['VNC'] = 'Consola VNC';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Nota';
$_ADDONLANG['LoadingFirewallRules'] = 'Carregando regras de firewall...';
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Использовано';
$_ADDONLANG['Start'] = 'Запуск';
$_ADDONLANG['Stop'] = 'Остановка';
$_ADDONLANG['Restart'] = 'Перезапуск';
$_ADDONLANG['VNC'] = 'Консоль VNC';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Примечание';
$_ADDONLANG['LoadingFirewallRules'] = 'Загрузка правил файрвола...';
@@ -10,7 +10,7 @@ $_ADDONLANG['Used'] = 'Usado';
$_ADDONLANG['Start'] = 'Iniciar';
$_ADDONLANG['Stop'] = 'Detener';
$_ADDONLANG['Restart'] = 'Reiniciar';
$_ADDONLANG['VNC'] = 'Consola VNC';
$_ADDONLANG['VNC'] = 'VNC';
$_ADDONLANG['General']['Note'] = 'Nota';
$_ADDONLANG['LoadingFirewallRules'] = 'Cargando reglas de firewall...';
@@ -644,7 +644,7 @@
<script type="text/javascript">
var productURL = '{$WEB_ROOT}/clientarea.php?action=productdetails&id={$serviceid}';
var serverInfoInitial = JSON.parse('{$serverInfo|@json_encode}');
var serverInfoInitial = JSON.parse('{$serverInfo|json_encode}');
var lang = {
moduleactionfailed: 'Action failed!',
moduleactionsuccess: 'Action completed successfully',
@@ -1547,6 +1547,13 @@
<i class="fa fa-cog"></i> {$_LANG['Navbar']['Settings']}
</a>
</li>
{if $vncMode == 'embedded'}
<li class="nav-item">
<a class="nav-link" id="vnc-tab" data-toggle="tab" href="#vnc" role="tab" aria-controls="vnc" aria-selected="false" onclick="loadVNCConsole()">
<i class="fa fa-desktop"></i> {$_LANG['VNC']}
</a>
</li>
{/if}
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="overview" role="tabpanel" aria-labelledby="overview-tab">
@@ -1732,11 +1739,17 @@
onclick="return confirmRestart();">
<i class="fa fa-sync mr-1"></i> {$_LANG['Restart']}
</button>
{if $vncMode == 'embedded'}
<button class="btn btn-info" onclick="$('#vnc-tab').tab('show'); loadVNCConsole();">
<i class="fa fa-desktop mr-1"></i> {$_LANG['VNC']}
</button>
{else}
<a href="{$WEB_ROOT}/clientarea.php?action=productdetails&id={$serviceid}&modop=custom&a=VNC"
target="_blank"
class="btn btn-info">
<i class="fa fa-desktop mr-1"></i> {$_LANG['VNC']}
</a>
{/if}
</div>
</div>
</div>
@@ -2114,6 +2127,32 @@
</div>
</div>
</div>
{if $vncMode == 'embedded'}
<!-- VNC Tab -->
<div class="tab-pane fade" id="vnc" role="tabpanel" aria-labelledby="vnc-tab">
<div class="card" id="vncCard">
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="fa fa-desktop mr-2"></i>{$_LANG['VNC']}</h5>
<div>
<button class="btn btn-sm btn-light" onclick="openVNCNewTab()" title="Open in New Tab">
<i class="fa fa-external-link-alt"></i>
</button>
<button class="btn btn-sm btn-light ml-1" onclick="toggleVNCFullscreen()" title="Fullscreen" id="vncFullscreenBtn">
<i class="fa fa-expand"></i>
</button>
</div>
</div>
<div class="card-body p-0" id="vncContainer">
<div id="vncLoading" class="text-center p-5">
<i class="fa fa-spinner fa-spin fa-3x"></i>
<p class="mt-3">Loading VNC Console...</p>
</div>
<iframe id="vncIframe" src="" style="width: 100%; height: 600px; border: none; display: none;"></iframe>
</div>
</div>
</div>
{/if}
</div>
</div>
</div>
@@ -2161,5 +2200,103 @@ jQuery(document).ready(function($) {
}
});
// Handle hash navigation for VNC tab (when coming from sidebar)
function handleVNCHash() {
if (window.location.hash === '#vnc') {
$('#vnc-tab').tab('show');
loadVNCConsole();
}
}
// Check on page load
if (window.location.hash === '#vnc') {
setTimeout(handleVNCHash, 100);
}
// Listen for hash changes (when clicking sidebar link while already on page)
$(window).on('hashchange', handleVNCHash);
// Intercept sidebar VNC link clicks
$('a[href$="#vnc"]').on('click', function(e) {
e.preventDefault();
$('#vnc-tab').tab('show');
loadVNCConsole();
history.pushState(null, null, '#vnc');
});
});
</script>
// VNC Tab functions
var vncLoaded = false;
function loadVNCConsole() {
if (vncLoaded) return;
var iframe = document.getElementById('vncIframe');
var loading = document.getElementById('vncLoading');
var container = document.getElementById('vncContainer');
if (!iframe || !container) return;
// Fetch VNC URL via API and load directly
jQuery.get(productURL + '&modop=custom&a=ClientAreaAPI&api=VNC Console&token={$token}', function(data) {
console.log('VNC API response:', data);
if (data && data.vnc_url) {
iframe.src = data.vnc_url;
iframe.style.display = 'block';
if (loading) loading.style.display = 'none';
vncLoaded = true;
} else if (data && data.message) {
container.innerHTML = '<div class="alert alert-danger m-3">' + data.message + '</div>';
} else {
container.innerHTML = '<div class="alert alert-warning m-3">VNC URL not available. Response: ' + JSON.stringify(data) + '</div>';
}
}).fail(function(xhr, status, error) {
console.log('VNC API error:', status, error);
container.innerHTML = '<div class="alert alert-danger m-3">Failed to load VNC console: ' + error + '</div>';
});
}
// Open VNC in new tab
function openVNCNewTab() {
window.open(productURL + '&modop=custom&a=VNC', '_blank');
}
// Toggle fullscreen for VNC
function toggleVNCFullscreen() {
var card = document.getElementById('vncCard');
var iframe = document.getElementById('vncIframe');
var btn = document.getElementById('vncFullscreenBtn');
if (!document.fullscreenElement) {
if (card.requestFullscreen) {
card.requestFullscreen();
} else if (card.webkitRequestFullscreen) {
card.webkitRequestFullscreen();
} else if (card.msRequestFullscreen) {
card.msRequestFullscreen();
}
iframe.style.height = '100vh';
btn.innerHTML = '<i class="fa fa-compress"></i>';
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
iframe.style.height = '600px';
btn.innerHTML = '<i class="fa fa-expand"></i>';
}
}
// Handle fullscreen change events
document.addEventListener('fullscreenchange', function() {
var iframe = document.getElementById('vncIframe');
var btn = document.getElementById('vncFullscreenBtn');
if (!document.fullscreenElement && iframe && btn) {
iframe.style.height = '600px';
btn.innerHTML = '<i class="fa fa-expand"></i>';
}
});
</script>