feat: streamline network panel, conditional self-service, remove IP add endpoints

- Populate network panel from server data response instead of separate API call
- Conditionally render self-service billing panel based on selfServiceMode config
- Pass selfServiceMode to Smarty template vars
- Remove addIPv4, addIPv6, serverIPs client endpoints and UI buttons
- Remove upgrade/downgrade link from resources panel
- Bump cache-busting version to v0.0.20
- Update CHANGELOG.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
EZSCALE
2026-02-07 15:23:56 -06:00
parent 209e01deb6
commit e73e85c5a9
5 changed files with 48 additions and 193 deletions

View File

@@ -8,7 +8,7 @@ All notable changes to the VirtFusion Direct Provisioning Module for WHMCS.
- **Power management** — Start, restart, graceful shutdown, and force power off controls in client area - **Power management** — Start, restart, graceful shutdown, and force power off controls in client area
- **Server rebuild** — Reinstall with any available OS template from client area with confirmation dialog - **Server rebuild** — Reinstall with any available OS template from client area with confirmation dialog
- **Server rename** — Change server display name via client area - **Server rename** — Change server display name via client area
- **Network management** — View, add, and remove IPv4 addresses and IPv6 subnets from client area - **Network management** — View and remove IPv4 addresses; view IPv6 subnets from client area
- **VNC console** — Browser-based console access (VirtFusion v6.1.0+) - **VNC console** — Browser-based console access (VirtFusion v6.1.0+)
- **VNC runtime check** — VNC panel auto-hides when VNC is disabled on the server - **VNC runtime check** — VNC panel auto-hides when VNC is disabled on the server
- **Backup management** — Assign and remove backup plans via API - **Backup management** — Assign and remove backup plans via API
@@ -23,6 +23,7 @@ All notable changes to the VirtFusion Direct Provisioning Module for WHMCS.
- **Traffic usage display** — Bandwidth used vs allocated - **Traffic usage display** — Bandwidth used vs allocated
- **Checkout validation** — `ShoppingCartValidateCheckout` hook ensures OS selection before order placement - **Checkout validation** — `ShoppingCartValidateCheckout` hook ensures OS selection before order placement
- **SSH key paste at checkout** — Users can paste a raw SSH public key during checkout; key is created via `POST /ssh_keys` during provisioning - **SSH key paste at checkout** — Users can paste a raw SSH public key during checkout; key is created via `POST /ssh_keys` during provisioning
- **SSH Ed25519 key generator** — Client-side keypair generation on checkout page using Web Crypto API; auto-fills public key and presents private key for download/copy
- **Order form sliders** — Configurable option dropdowns replaced with styled range sliders for resource selection - **Order form sliders** — Configurable option dropdowns replaced with styled range sliders for resource selection
- **Self-service billing** — Credit balance display, usage breakdown, and credit top-up from client area - **Self-service billing** — Credit balance display, usage breakdown, and credit top-up from client area
- **Self-service config options** — Product config options 4-6: Self-Service Mode, Auto Top-Off Threshold, Auto Top-Off Amount - **Self-service config options** — Product config options 4-6: Self-Service Mode, Auto Top-Off Threshold, Auto Top-Off Amount
@@ -41,6 +42,8 @@ All notable changes to the VirtFusion Direct Provisioning Module for WHMCS.
- `changePackage()` now applies individual resource modifications from configurable options after updating the package - `changePackage()` now applies individual resource modifications from configurable options after updating the package
- `initServerBuild()` accepts optional VF user ID parameter for SSH key creation - `initServerBuild()` accepts optional VF user ID parameter for SSH key creation
- `ServerResource::process()` returns raw numeric resource values and `vncEnabled` boolean - `ServerResource::process()` returns raw numeric resource values and `vncEnabled` boolean
- Network panel now populated from server data response instead of separate API call
- Self-service billing panel conditionally rendered based on `selfServiceMode` config option
- Comprehensive README rewrite with installation, configuration, troubleshooting, and API reference - Comprehensive README rewrite with installation, configuration, troubleshooting, and API reference
### Fixed ### Fixed
@@ -54,10 +57,12 @@ All notable changes to the VirtFusion Direct Provisioning Module for WHMCS.
- Memory conversion checks key name instead of display name - Memory conversion checks key name instead of display name
- Fix TestConnection failing for new/unsaved servers — use `$params` directly instead of database lookup (serverid=0 is falsy) - Fix TestConnection failing for new/unsaved servers — use `$params` directly instead of database lookup (serverid=0 is falsy)
- Fix traffic "Used" showing `-` instead of `0 GB` when traffic is allocated but no usage reported yet - Fix traffic "Used" showing `-` instead of `0 GB` when traffic is allocated but no usage reported yet
- Add cache-busting `?v=0.0.19` to JS/CSS includes in overview.tpl to prevent stale browser cache - Bump cache-busting version to `?v=0.0.20` for JS/CSS includes in overview.tpl
### Removed ### Removed
- Firewall feature (non-functional — rulesets must be created in VirtFusion admin panel) - Firewall feature (non-functional — rulesets must be created in VirtFusion admin panel)
- IP add endpoints (`addIPv4`, `addIPv6`, `serverIPs`) and add buttons — IPs are managed by VirtFusion during provisioning
- Upgrade/Downgrade link from resources panel
## [0.0.18] - 2025-10-01 ## [0.0.18] - 2025-10-01

View File

@@ -180,50 +180,6 @@ switch ($action) {
// IP Address Management // 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. * Remove an IPv4 address.
*/ */
@@ -249,51 +205,6 @@ switch ($action) {
$vf->output(['success' => false, 'errors' => 'Failed to remove IPv4 address'], true, true, 500); $vf->output(['success' => false, 'errors' => 'Failed to remove IPv4 address'], true, true, 500);
break; 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 // VNC Console
// ================================================================= // =================================================================

View File

@@ -531,6 +531,7 @@ class ModuleFunctions extends Module
'systemURL' => Database::getSystemUrl(), 'systemURL' => Database::getSystemUrl(),
'serviceStatus' => $params['status'], 'serviceStatus' => $params['status'],
'serverHostname' => $serverHostname, 'serverHostname' => $serverHostname,
'selfServiceMode' => (int) ($params['configoption4'] ?? 0),
], ],
]; ];
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -77,6 +77,41 @@ function vfServerData(serviceId, systemUrl) {
$("#vf-resources-panel").show(); $("#vf-resources-panel").show();
// Populate network panel from server data
var ipv4List = $("#vf-ipv4-list");
var ipv6List = $("#vf-ipv6-list");
ipv4List.empty();
ipv6List.empty();
var net = response.data.primaryNetwork || {};
var ipv4Arr = net.ipv4Unformatted || [];
var ipv6Arr = net.ipv6Unformatted || [];
if (ipv4Arr.length > 0) {
$.each(ipv4Arr, 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 (ipv6Arr.length > 0) {
$.each(ipv6Arr, function (i, subnet) {
var row = $('<div class="vf-ip-row"></div>');
row.append('<span class="vf-ip-address">' + $('<span>').text(subnet).html() + '</span>');
ipv6List.append(row);
});
} else {
ipv6List.append('<span class="text-muted">No IPv6 subnets</span>');
}
$("#vf-network-content").show();
$("#vf-server-info").show(); $("#vf-server-info").show();
} else { } else {
$("#vf-server-info-error").show(); $("#vf-server-info-error").show();
@@ -301,92 +336,6 @@ function impersonateServerOwner(serviceId, systemUrl) {
// Network / IP Management // 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) { function vfRemoveIP(serviceId, systemUrl, action, identifier) {
if (!confirm("Are you sure you want to remove this IP address?")) { if (!confirm("Are you sure you want to remove this IP address?")) {
return; return;
@@ -406,7 +355,7 @@ function vfRemoveIP(serviceId, systemUrl, action, identifier) {
alertDiv.removeClass("alert-danger").addClass("alert-success"); alertDiv.removeClass("alert-danger").addClass("alert-success");
alertDiv.text(response.data.message || "IP address removed successfully."); alertDiv.text(response.data.message || "IP address removed successfully.");
alertDiv.show(); alertDiv.show();
vfLoadServerIPs(serviceId, systemUrl); vfServerData(serviceId, systemUrl);
} else { } else {
alertDiv.removeClass("alert-success").addClass("alert-danger"); alertDiv.removeClass("alert-success").addClass("alert-danger");
alertDiv.text(response.errors || "Failed to remove IP address."); alertDiv.text(response.errors || "Failed to remove IP address.");

View File

@@ -1,5 +1,5 @@
<link href="{$systemURL}modules/servers/VirtFusionDirect/templates/css/module.css?v=0.0.19" rel="stylesheet"> <link href="{$systemURL}modules/servers/VirtFusionDirect/templates/css/module.css?v=0.0.20" rel="stylesheet">
<script src="{$systemURL}modules/servers/VirtFusionDirect/templates/js/module.js?v=0.0.19"></script> <script src="{$systemURL}modules/servers/VirtFusionDirect/templates/js/module.js?v=0.0.20"></script>
{if $serviceStatus eq 'Active'} {if $serviceStatus eq 'Active'}
@@ -175,30 +175,18 @@
</div> </div>
<div class="panel-body card-body p-4"> <div class="panel-body card-body p-4">
<div id="vf-network-alert" class="alert" style="display: none;"></div> <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 id="vf-network-content" style="display: none;">
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
<h5 class="vf-bold">IPv4 Addresses</h5> <h5 class="vf-bold">IPv4 Addresses</h5>
<div id="vf-ipv4-list" class="mb-2"></div> <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>
<div class="col-md-6"> <div class="col-md-6">
<h5 class="vf-bold">IPv6 Subnets</h5> <h5 class="vf-bold">IPv6 Subnets</h5>
<div id="vf-ipv6-list" class="mb-2"></div> <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> </div>
</div> </div>
<script>vfLoadServerIPs('{$serviceid}', '{$systemURL}');</script>
</div> </div>
</div> </div>
@@ -247,7 +235,6 @@
</div> </div>
</div> </div>
</div> </div>
<a href="clientarea.php?action=upgrade&id={$serviceid}" class="btn btn-outline-primary mt-2">Upgrade / Downgrade Resources</a>
</div> </div>
</div> </div>
@@ -267,6 +254,7 @@
</div> </div>
{* Self Service — Billing & Usage Panel *} {* Self Service — Billing & Usage Panel *}
{if $selfServiceMode > 0}
<div id="vf-selfservice-panel" class="panel card panel-default mb-3" style="display: none;"> <div id="vf-selfservice-panel" class="panel card panel-default mb-3" style="display: none;">
<div class="panel-heading card-header"> <div class="panel-heading card-header">
<h3 class="panel-title card-title m-0">Billing & Usage</h3> <h3 class="panel-title card-title m-0">Billing & Usage</h3>
@@ -313,6 +301,7 @@
<script>vfLoadSelfServiceUsage('{$serviceid}', '{$systemURL}');</script> <script>vfLoadSelfServiceUsage('{$serviceid}', '{$systemURL}');</script>
</div> </div>
</div> </div>
{/if}
{elseif $serviceStatus eq 'Suspended'} {elseif $serviceStatus eq 'Suspended'}