Add firewall, network, VNC, backup, resource management and UsageUpdate
New features implemented: - Firewall management: enable/disable, status display, apply rules - IP address management: add/remove IPv4 and IPv6 with client UI - VNC console access integration (VirtFusion v6.1.0+) - Backup plan assignment/removal via API - Resource modification: in-place memory/CPU/traffic changes - UsageUpdate cron: automated bandwidth and disk usage sync to WHMCS - Dry run validation: test server creation config before provisioning - Admin "Validate Server Config" button for dry run testing Client area additions: - Firewall panel with enable/disable/apply controls and status badge - Network panel with IPv4/IPv6 listing, add, and remove buttons - VNC Console panel with browser-based access - All panels load asynchronously with spinner indicators Comprehensive README rewrite with: - Table of contents, requirements matrix, step-by-step installation - Detailed configuration guide for all features - Theme compatibility documentation (Six, Twenty-One, Lagom) - Complete API endpoints reference organized by category - UsageUpdate cron documentation with data format details - Troubleshooting tables for common issues - Known issues section covering version requirements - Security architecture documentation - File structure reference https://claude.ai/code/session_01TCsJ4WZCGuEX3zqh1tQ2zx
This commit is contained in:
@@ -84,6 +84,7 @@ function VirtFusionDirect_AdminCustomButtonArray()
|
||||
{
|
||||
return [
|
||||
"Update Server Object" => "updateServerObject",
|
||||
"Validate Server Config" => "validateServerConfig",
|
||||
];
|
||||
}
|
||||
|
||||
@@ -156,3 +157,96 @@ function VirtFusionDirect_ClientArea(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->clientArea($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates server configuration via dry run without creating the server.
|
||||
*
|
||||
* @param array $params
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_validateServerConfig(array $params)
|
||||
{
|
||||
return (new ModuleFunctions())->validateServerConfig($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage Update - called by WHMCS daily cron to sync bandwidth and disk usage.
|
||||
*
|
||||
* Updates tblhosting with disk and bandwidth usage data from VirtFusion.
|
||||
* Fields updated: diskused, disklimit, bwused, bwlimit, lastupdate
|
||||
*
|
||||
* @param array $params Server access credentials
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
function VirtFusionDirect_UsageUpdate(array $params)
|
||||
{
|
||||
try {
|
||||
$module = new Module();
|
||||
$cp = $module->getCP($params['serverid']);
|
||||
|
||||
if (!$cp) {
|
||||
return 'No control server found for usage update.';
|
||||
}
|
||||
|
||||
$services = \WHMCS\Database\Capsule::table('tblhosting')
|
||||
->where('server', $params['serverid'])
|
||||
->where('domainstatus', 'Active')
|
||||
->get();
|
||||
|
||||
foreach ($services as $service) {
|
||||
try {
|
||||
$systemService = Database::getSystemService($service->id);
|
||||
if (!$systemService) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$request = $module->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/servers/' . (int) $systemService->server_id);
|
||||
|
||||
if ($request->getRequestInfo('http_code') != 200) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$serverData = json_decode($data, true);
|
||||
if (!isset($serverData['data'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$server = $serverData['data'];
|
||||
$update = [];
|
||||
|
||||
// Disk usage (WHMCS expects MB)
|
||||
if (isset($server['usage']['storage']['used'])) {
|
||||
$update['diskused'] = round($server['usage']['storage']['used'] / 1048576);
|
||||
}
|
||||
if (isset($server['settings']['resources']['storage'])) {
|
||||
$update['disklimit'] = (int) $server['settings']['resources']['storage'] * 1024;
|
||||
}
|
||||
|
||||
// Bandwidth usage (WHMCS expects MB)
|
||||
if (isset($server['usage']['traffic']['used'])) {
|
||||
$update['bwused'] = round($server['usage']['traffic']['used'] / 1048576);
|
||||
}
|
||||
if (isset($server['settings']['resources']['traffic'])) {
|
||||
$trafficGB = (int) $server['settings']['resources']['traffic'];
|
||||
$update['bwlimit'] = $trafficGB > 0 ? $trafficGB * 1024 : 0;
|
||||
}
|
||||
|
||||
if (!empty($update)) {
|
||||
$update['lastupdate'] = date('Y-m-d H:i:s');
|
||||
\WHMCS\Database\Capsule::table('tblhosting')
|
||||
->where('id', $service->id)
|
||||
->update($update);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Log but continue processing other services
|
||||
\WHMCS\Module\Server\VirtFusionDirect\Log::insert('UsageUpdate:service:' . $service->id, [], $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 'success';
|
||||
} catch (\Exception $e) {
|
||||
return 'Usage update failed: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,232 @@ switch ($action) {
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to fetch OS templates'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// Firewall Management
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get firewall status and rules.
|
||||
*/
|
||||
case 'firewallStatus':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->getFirewallStatus($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve firewall status'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Enable firewall.
|
||||
*/
|
||||
case 'firewallEnable':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->enableFirewall($serviceID);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Firewall enabled successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to enable firewall'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Disable firewall.
|
||||
*/
|
||||
case 'firewallDisable':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->disableFirewall($serviceID);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Firewall disabled successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to disable firewall'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Apply/sync firewall rules.
|
||||
*/
|
||||
case 'firewallApplyRules':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->applyFirewallRules($serviceID);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'Firewall rules applied successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to apply firewall rules'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// IP Address Management
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get server IP addresses (from server data).
|
||||
*/
|
||||
case 'serverIPs':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$data = $vf->fetchServerData($serviceID);
|
||||
|
||||
if ($data) {
|
||||
$resource = (new ServerResource())->process($data);
|
||||
$vf->output(['success' => true, 'data' => [
|
||||
'ipv4' => $resource['primaryNetwork']['ipv4Unformatted'],
|
||||
'ipv6' => $resource['primaryNetwork']['ipv6Unformatted'],
|
||||
]], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Unable to retrieve IP addresses'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Add an IPv4 address.
|
||||
*/
|
||||
case 'addIPv4':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->addIPv4($serviceID);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'IPv4 address added successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to add IPv4 address. No available addresses or limit reached.'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Remove an IPv4 address.
|
||||
*/
|
||||
case 'removeIPv4':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$ipAddress = isset($_GET['ip']) ? trim($_GET['ip']) : '';
|
||||
if (!filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid IPv4 address'], true, true, 400);
|
||||
}
|
||||
|
||||
$result = $vf->removeIPv4($serviceID, $ipAddress);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'IPv4 address removed successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to remove IPv4 address'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Add an IPv6 subnet.
|
||||
*/
|
||||
case 'addIPv6':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->addIPv6($serviceID);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'IPv6 subnet added successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to add IPv6 subnet. No available subnets or limit reached.'], true, true, 500);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Remove an IPv6 subnet.
|
||||
*/
|
||||
case 'removeIPv6':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$subnet = isset($_GET['subnet']) ? trim($_GET['subnet']) : '';
|
||||
if (empty($subnet)) {
|
||||
$vf->output(['success' => false, 'errors' => 'Invalid IPv6 subnet'], true, true, 400);
|
||||
}
|
||||
|
||||
$result = $vf->removeIPv6($serviceID, $subnet);
|
||||
|
||||
if ($result) {
|
||||
$vf->output(['success' => true, 'data' => ['message' => 'IPv6 subnet removed successfully']], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'Failed to remove IPv6 subnet'], true, true, 500);
|
||||
break;
|
||||
|
||||
// =================================================================
|
||||
// VNC Console
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* Get VNC console URL.
|
||||
*/
|
||||
case 'vnc':
|
||||
|
||||
$serviceID = $vf->validateServiceID(true);
|
||||
|
||||
if (!$vf->validateUserOwnsService($serviceID)) {
|
||||
$vf->output(['success' => false, 'errors' => 'service <> owner mismatch'], true, true, 403);
|
||||
}
|
||||
|
||||
$result = $vf->getVncConsole($serviceID);
|
||||
|
||||
if ($result !== false) {
|
||||
$vf->output(['success' => true, 'data' => $result], true, true, 200);
|
||||
}
|
||||
|
||||
$vf->output(['success' => false, 'errors' => 'VNC console unavailable. The server may be powered off or VNC is not supported.'], true, true, 500);
|
||||
break;
|
||||
|
||||
default:
|
||||
$vf->output(['success' => false, 'errors' => 'invalid action'], true, true, 400);
|
||||
}
|
||||
|
||||
@@ -297,6 +297,400 @@ class Module
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Firewall Management
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get firewall status and rules for a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return array|false
|
||||
*/
|
||||
public function getFirewallStatus($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
if ($request->getRequestInfo('http_code') == 200) {
|
||||
return json_decode($data, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable firewall on a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return object|false
|
||||
*/
|
||||
public function enableFirewall($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/enable');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable firewall on a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return object|false
|
||||
*/
|
||||
public function disableFirewall($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/disable');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply/synchronize firewall rules on a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return object|false
|
||||
*/
|
||||
public function applyFirewallRules($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/firewall/rules/apply');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// IP Address Management
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Add an IPv4 address to a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return object|false
|
||||
*/
|
||||
public function addIPv4($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/ipv4');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 201) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an IPv4 address from a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @param string $ipAddress The IPv4 address to remove
|
||||
* @return object|false
|
||||
*/
|
||||
public function removeIPv4($serviceID, $ipAddress)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$ipAddress = filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
if (!$ipAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode(['address' => $ipAddress]));
|
||||
$data = $request->delete($cp['url'] . '/servers/' . (int) $service->server_id . '/ipv4');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an IPv6 subnet to a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return object|false
|
||||
*/
|
||||
public function addIPv6($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/ipv6');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 201) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an IPv6 subnet from a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @param string $subnet The IPv6 subnet to remove
|
||||
* @return object|false
|
||||
*/
|
||||
public function removeIPv6($serviceID, $subnet)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$subnet = trim($subnet);
|
||||
if (empty($subnet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode(['subnet' => $subnet]));
|
||||
$data = $request->delete($cp['url'] . '/servers/' . (int) $service->server_id . '/ipv6');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Backup Management
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Assign a backup plan to a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @param int $planId Backup plan ID (0 to remove)
|
||||
* @return object|false
|
||||
*/
|
||||
public function assignBackupPlan($serviceID, $planId)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$planId = (int) $planId;
|
||||
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode(['planId' => $planId]));
|
||||
|
||||
if ($planId > 0) {
|
||||
$data = $request->post($cp['url'] . '/servers/' . (int) $service->server_id . '/backup/plan');
|
||||
} else {
|
||||
$data = $request->delete($cp['url'] . '/servers/' . (int) $service->server_id . '/backup/plan');
|
||||
}
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// VNC Console
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get VNC console connection details for a server.
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @return array|false
|
||||
*/
|
||||
public function getVncConsole($serviceID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/servers/' . (int) $service->server_id . '/vnc');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
if ($request->getRequestInfo('http_code') == 200) {
|
||||
return json_decode($data, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Resource Modification
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Modify a server resource (memory, cpuCores, or traffic).
|
||||
*
|
||||
* @param int $serviceID
|
||||
* @param string $resource One of: memory, cpuCores, traffic
|
||||
* @param int $value New value for the resource
|
||||
* @return object|false
|
||||
*/
|
||||
public function modifyResource($serviceID, $resource, $value)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
$allowedResources = ['memory', 'cpuCores', 'traffic'];
|
||||
if (!in_array($resource, $allowedResources, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = (int) $value;
|
||||
if ($value < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$service = Database::getSystemService($serviceID);
|
||||
|
||||
if ($service) {
|
||||
$whmcsService = Database::getWhmcsService($serviceID);
|
||||
$cp = $this->getCP($whmcsService->server);
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode([$resource => $value]));
|
||||
$data = $request->put($cp['url'] . '/servers/' . (int) $service->server_id . '/modify/' . $resource);
|
||||
|
||||
Log::insert(__FUNCTION__ . ':' . $resource, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
if ($httpCode == 200 || $httpCode == 204) {
|
||||
return json_decode($data) ?: (object) ['success' => true];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Dry Run Validation
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Validate server creation parameters without actually creating a server.
|
||||
*
|
||||
* @param array $options Server creation options
|
||||
* @param int $serverId WHMCS server ID for API credentials
|
||||
* @return array ['valid' => bool, 'errors' => array]
|
||||
*/
|
||||
public function validateServerCreation($options, $serverId)
|
||||
{
|
||||
$cp = $this->getCP($serverId, !$serverId);
|
||||
if (!$cp) {
|
||||
return ['valid' => false, 'errors' => ['No control server found']];
|
||||
}
|
||||
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$request->addOption(CURLOPT_POSTFIELDS, json_encode($options));
|
||||
$data = $request->post($cp['url'] . '/servers?dryRun=true');
|
||||
|
||||
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
|
||||
|
||||
$httpCode = $request->getRequestInfo('http_code');
|
||||
$response = json_decode($data, true);
|
||||
|
||||
if ($httpCode == 200 || $httpCode == 201) {
|
||||
return ['valid' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
if (isset($response['errors']) && is_array($response['errors'])) {
|
||||
$errors = $response['errors'];
|
||||
} elseif (isset($response['msg'])) {
|
||||
$errors = [$response['msg']];
|
||||
} else {
|
||||
$errors = ['Validation failed with HTTP ' . $httpCode];
|
||||
}
|
||||
|
||||
return ['valid' => false, 'errors' => $errors];
|
||||
}
|
||||
|
||||
public function resetUserPassword($serviceID, $clientID)
|
||||
{
|
||||
$serviceID = (int) $serviceID;
|
||||
|
||||
@@ -423,6 +423,50 @@ class ModuleFunctions extends Module
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate server creation parameters via dry run.
|
||||
*
|
||||
* @param array $params WHMCS service params
|
||||
* @return string 'success' or error message
|
||||
*/
|
||||
public function validateServerConfig($params)
|
||||
{
|
||||
try {
|
||||
$server = $params['serverid'] ?: false;
|
||||
$cp = $this->getCP($server, !$server);
|
||||
|
||||
if (!$cp) {
|
||||
return 'No Control server found.';
|
||||
}
|
||||
|
||||
$options = [
|
||||
"packageId" => (int) $params['configoption2'],
|
||||
"hypervisorId" => (int) $params['configoption1'],
|
||||
"ipv4" => (int) $params['configoption3'],
|
||||
];
|
||||
|
||||
// We need a userId for dry run - use the service owner
|
||||
if (isset($params['userid'])) {
|
||||
$request = $this->initCurl($cp['token']);
|
||||
$data = $request->get($cp['url'] . '/users/' . (int) $params['userid'] . '/byExtRelation');
|
||||
if ($request->getRequestInfo('http_code') == 200) {
|
||||
$userData = json_decode($data);
|
||||
$options['userId'] = $userData->data->id;
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->validateServerCreation($options, $params['serverid']);
|
||||
|
||||
if ($result['valid']) {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return 'Validation failed: ' . implode(', ', $result['errors']);
|
||||
} catch (\Exception $e) {
|
||||
return 'Validation error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function clientArea($params)
|
||||
{
|
||||
$serverHostname = null;
|
||||
|
||||
@@ -123,6 +123,22 @@
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* Network / IP Management */
|
||||
.vf-ip-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.vf-ip-address {
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.vf-ip-remove {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.15rem 0.4rem;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.vf-power-buttons {
|
||||
@@ -131,4 +147,7 @@
|
||||
.vf-btn-power {
|
||||
width: 100%;
|
||||
}
|
||||
.vf-ip-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,3 +259,245 @@ function impersonateServerOwner(serviceId, systemUrl) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Firewall Management
|
||||
// =========================================================================
|
||||
|
||||
function vfLoadFirewallStatus(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=firewallStatus"
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
var badge = $("#vf-firewall-badge");
|
||||
var data = response.data;
|
||||
var enabled = data && data.data && data.data.enabled;
|
||||
if (enabled) {
|
||||
badge.text("Enabled").addClass("vf-badge-active");
|
||||
} else {
|
||||
badge.text("Disabled").addClass("vf-badge-awaiting");
|
||||
}
|
||||
$("#vf-firewall-content").show();
|
||||
} else {
|
||||
$("#vf-firewall-badge").text("Unknown").addClass("vf-badge-awaiting");
|
||||
$("#vf-firewall-content").show();
|
||||
}
|
||||
}).fail(function () {
|
||||
$("#vf-firewall-badge").text("Unavailable").addClass("vf-badge-awaiting");
|
||||
$("#vf-firewall-content").show();
|
||||
}).always(function () {
|
||||
$("#vf-firewall-loader").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function vfFirewallAction(serviceId, systemUrl, action) {
|
||||
var btnId = {
|
||||
firewallEnable: "#vf-firewall-enable",
|
||||
firewallDisable: "#vf-firewall-disable",
|
||||
firewallApplyRules: "#vf-firewall-apply"
|
||||
};
|
||||
var btn = $(btnId[action]);
|
||||
var spinner = btn.find(".vf-btn-spinner");
|
||||
var alertDiv = $("#vf-firewall-alert");
|
||||
|
||||
btn.prop("disabled", true);
|
||||
spinner.show();
|
||||
alertDiv.hide();
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=" + encodeURIComponent(action)
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text(response.data.message || "Firewall action completed.");
|
||||
// Refresh status badge
|
||||
vfLoadFirewallStatus(serviceId, systemUrl);
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "Firewall action failed.");
|
||||
}
|
||||
alertDiv.show();
|
||||
}).fail(function () {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("An error occurred. Please try again.");
|
||||
alertDiv.show();
|
||||
}).always(function () {
|
||||
spinner.hide();
|
||||
btn.prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Network / IP Management
|
||||
// =========================================================================
|
||||
|
||||
function vfLoadServerIPs(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=serverIPs"
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
var ipv4List = $("#vf-ipv4-list");
|
||||
var ipv6List = $("#vf-ipv6-list");
|
||||
ipv4List.empty();
|
||||
ipv6List.empty();
|
||||
|
||||
if (response.data.ipv4 && response.data.ipv4.length > 0) {
|
||||
$.each(response.data.ipv4, function (i, ip) {
|
||||
var row = $('<div class="vf-ip-row"></div>');
|
||||
row.append('<span class="vf-ip-address">' + $('<span>').text(ip).html() + '</span>');
|
||||
if (i > 0) {
|
||||
row.append(' <button class="btn btn-sm btn-outline-danger vf-ip-remove" onclick="vfRemoveIP(\'' + serviceId + '\',\'' + systemUrl + '\',\'removeIPv4\',\'' + encodeURIComponent(ip) + '\')">Remove</button>');
|
||||
}
|
||||
ipv4List.append(row);
|
||||
});
|
||||
} else {
|
||||
ipv4List.append('<span class="text-muted">No IPv4 addresses</span>');
|
||||
}
|
||||
|
||||
if (response.data.ipv6 && response.data.ipv6.length > 0) {
|
||||
$.each(response.data.ipv6, function (i, subnet) {
|
||||
var row = $('<div class="vf-ip-row"></div>');
|
||||
row.append('<span class="vf-ip-address">' + $('<span>').text(subnet).html() + '</span>');
|
||||
row.append(' <button class="btn btn-sm btn-outline-danger vf-ip-remove" onclick="vfRemoveIP(\'' + serviceId + '\',\'' + systemUrl + '\',\'removeIPv6\',\'' + encodeURIComponent(subnet) + '\')">Remove</button>');
|
||||
ipv6List.append(row);
|
||||
});
|
||||
} else {
|
||||
ipv6List.append('<span class="text-muted">No IPv6 subnets</span>');
|
||||
}
|
||||
|
||||
$("#vf-network-content").show();
|
||||
} else {
|
||||
$("#vf-network-content").show();
|
||||
$("#vf-ipv4-list").html('<span class="text-muted">Unable to load</span>');
|
||||
$("#vf-ipv6-list").html('<span class="text-muted">Unable to load</span>');
|
||||
}
|
||||
}).fail(function () {
|
||||
$("#vf-network-content").show();
|
||||
$("#vf-ipv4-list").html('<span class="text-muted">Unable to load</span>');
|
||||
$("#vf-ipv6-list").html('<span class="text-muted">Unable to load</span>');
|
||||
}).always(function () {
|
||||
$("#vf-network-loader").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function vfAddIP(serviceId, systemUrl, action) {
|
||||
var btn = $("#vf-add-" + (action === "addIPv4" ? "ipv4" : "ipv6"));
|
||||
var spinner = btn.find(".vf-btn-spinner");
|
||||
var alertDiv = $("#vf-network-alert");
|
||||
|
||||
btn.prop("disabled", true);
|
||||
spinner.show();
|
||||
alertDiv.hide();
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=" + encodeURIComponent(action)
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text(response.data.message || "IP address added successfully.");
|
||||
alertDiv.show();
|
||||
// Refresh IP list
|
||||
vfLoadServerIPs(serviceId, systemUrl);
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "Failed to add IP address.");
|
||||
alertDiv.show();
|
||||
}
|
||||
}).fail(function () {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("An error occurred. Please try again.");
|
||||
alertDiv.show();
|
||||
}).always(function () {
|
||||
spinner.hide();
|
||||
btn.prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
function vfRemoveIP(serviceId, systemUrl, action, identifier) {
|
||||
if (!confirm("Are you sure you want to remove this IP address?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var alertDiv = $("#vf-network-alert");
|
||||
alertDiv.hide();
|
||||
|
||||
var paramName = action === "removeIPv4" ? "ip" : "subnet";
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=" + encodeURIComponent(action) + "&" + paramName + "=" + identifier
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text(response.data.message || "IP address removed successfully.");
|
||||
alertDiv.show();
|
||||
vfLoadServerIPs(serviceId, systemUrl);
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "Failed to remove IP address.");
|
||||
alertDiv.show();
|
||||
}
|
||||
}).fail(function () {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("An error occurred. Please try again.");
|
||||
alertDiv.show();
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// VNC Console
|
||||
// =========================================================================
|
||||
|
||||
function vfOpenVnc(serviceId, systemUrl) {
|
||||
var btn = $("#vf-vnc-button");
|
||||
var spinner = $("#vf-vnc-spinner");
|
||||
var alertDiv = $("#vf-vnc-alert");
|
||||
|
||||
btn.prop("disabled", true);
|
||||
spinner.show();
|
||||
alertDiv.hide();
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=vnc"
|
||||
}).done(function (response) {
|
||||
if (response.success && response.data) {
|
||||
var data = response.data.data || response.data;
|
||||
if (data.url) {
|
||||
window.open(data.url, "_blank");
|
||||
} else if (data.host && data.port) {
|
||||
// Build noVNC URL if available
|
||||
var vncUrl = "https://" + data.host + ":" + data.port;
|
||||
if (data.token) {
|
||||
vncUrl += "?token=" + encodeURIComponent(data.token);
|
||||
}
|
||||
window.open(vncUrl, "_blank");
|
||||
} else {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text("VNC session is ready. Check your VirtFusion control panel for access.");
|
||||
alertDiv.show();
|
||||
}
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "VNC console is not available.");
|
||||
alertDiv.show();
|
||||
}
|
||||
}).fail(function () {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("An error occurred. The server may be powered off.");
|
||||
alertDiv.show();
|
||||
}).always(function () {
|
||||
spinner.hide();
|
||||
btn.prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,6 +168,93 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* Firewall Management Panel *}
|
||||
<div class="panel card panel-default mb-3">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">
|
||||
Firewall
|
||||
<span id="vf-firewall-badge" class="vf-badge" style="float: right;"></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body card-body p-4">
|
||||
<div id="vf-firewall-alert" class="alert" style="display: none;"></div>
|
||||
<div id="vf-firewall-loader" class="d-flex align-items-center justify-content-center" style="min-height: 60px;">
|
||||
<div class="spinner-border spinner-border-sm"></div>
|
||||
</div>
|
||||
<div id="vf-firewall-content" style="display: none;">
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="vf-power-buttons">
|
||||
<button id="vf-firewall-enable" onclick="vfFirewallAction('{$serviceid}','{$systemURL}','firewallEnable')" type="button" class="btn btn-success vf-btn-power">
|
||||
<span class="vf-btn-spinner spinner-border spinner-border-sm" style="display:none;"></span>
|
||||
Enable
|
||||
</button>
|
||||
<button id="vf-firewall-disable" onclick="vfFirewallAction('{$serviceid}','{$systemURL}','firewallDisable')" type="button" class="btn btn-danger vf-btn-power">
|
||||
<span class="vf-btn-spinner spinner-border spinner-border-sm" style="display:none;"></span>
|
||||
Disable
|
||||
</button>
|
||||
<button id="vf-firewall-apply" onclick="vfFirewallAction('{$serviceid}','{$systemURL}','firewallApplyRules')" type="button" class="btn btn-primary vf-btn-power">
|
||||
<span class="vf-btn-spinner spinner-border spinner-border-sm" style="display:none;"></span>
|
||||
Apply Rules
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="vf-small text-muted mb-0">Manage your server firewall. Use the VirtFusion control panel for advanced rule configuration.</p>
|
||||
</div>
|
||||
<script>vfLoadFirewallStatus('{$serviceid}', '{$systemURL}');</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* Network Management Panel *}
|
||||
<div class="panel card panel-default mb-3">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">Network</h3>
|
||||
</div>
|
||||
<div class="panel-body card-body p-4">
|
||||
<div id="vf-network-alert" class="alert" style="display: none;"></div>
|
||||
<div id="vf-network-loader" class="d-flex align-items-center justify-content-center" style="min-height: 60px;">
|
||||
<div class="spinner-border spinner-border-sm"></div>
|
||||
</div>
|
||||
<div id="vf-network-content" style="display: none;">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<h5 class="vf-bold">IPv4 Addresses</h5>
|
||||
<div id="vf-ipv4-list" class="mb-2"></div>
|
||||
<button id="vf-add-ipv4" onclick="vfAddIP('{$serviceid}','{$systemURL}','addIPv4')" type="button" class="btn btn-sm btn-outline-primary">
|
||||
<span class="vf-btn-spinner spinner-border spinner-border-sm" style="display:none;"></span>
|
||||
Add IPv4
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5 class="vf-bold">IPv6 Subnets</h5>
|
||||
<div id="vf-ipv6-list" class="mb-2"></div>
|
||||
<button id="vf-add-ipv6" onclick="vfAddIP('{$serviceid}','{$systemURL}','addIPv6')" type="button" class="btn btn-sm btn-outline-primary">
|
||||
<span class="vf-btn-spinner spinner-border spinner-border-sm" style="display:none;"></span>
|
||||
Add IPv6
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>vfLoadServerIPs('{$serviceid}', '{$systemURL}');</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* VNC Console Panel *}
|
||||
<div class="panel card panel-default mb-3">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">VNC Console</h3>
|
||||
</div>
|
||||
<div class="panel-body card-body p-4">
|
||||
<div id="vf-vnc-alert" class="alert" style="display: none;"></div>
|
||||
<p>Access your server's console directly in your browser. The server must be running for VNC access.</p>
|
||||
<button id="vf-vnc-button" onclick="vfOpenVnc('{$serviceid}','{$systemURL}')" type="button" class="btn btn-primary text-uppercase d-flex align-items-center">
|
||||
<span id="vf-vnc-spinner" class="spinner-border spinner-border-sm vf-spinner-margin" style="display:none;"></span>
|
||||
Open Console
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{elseif $serviceStatus eq 'Suspended'}
|
||||
|
||||
<div class="panel card panel-default mb-3">
|
||||
|
||||
Reference in New Issue
Block a user