feat: add VNC check, SSH key paste, resources panel, sliders, and self-service billing
- VNC panel auto-hides when VNC is disabled on the server - SSH key paste textarea at checkout with API key creation during provisioning - Resources panel with current allocation, traffic progress bar, and upgrade link - changePackage() now applies individual resource modifications from configurable options - Order form configurable option dropdowns replaced with styled range sliders - Self-service billing: credit balance, usage breakdown, credit top-up from client area - Self-service config options (mode, auto top-off threshold/amount) on products - Auto top-off via WHMCS cron when credit falls below threshold - CHANGELOG.md covering all versions from 0.0.6 to present Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -139,6 +139,53 @@
|
||||
padding: 0.15rem 0.4rem;
|
||||
}
|
||||
|
||||
/* Resource panel */
|
||||
.vf-resource-item .progress {
|
||||
background-color: rgba(0,0,0,0.08);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Order form slider UI */
|
||||
.vf-slider-container {
|
||||
padding: 8px 0;
|
||||
}
|
||||
.vf-slider-value {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.vf-slider {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: #ddd;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.vf-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #337ab7;
|
||||
cursor: pointer;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
.vf-slider::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #337ab7;
|
||||
cursor: pointer;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.vf-power-buttons {
|
||||
|
||||
@@ -40,6 +40,43 @@ function vfServerData(serviceId, systemUrl) {
|
||||
statusBadge.addClass("vf-badge-awaiting");
|
||||
}
|
||||
|
||||
// Show/hide VNC panel based on API response
|
||||
if (response.data.vncEnabled) {
|
||||
$("#vf-vnc-panel").show();
|
||||
}
|
||||
|
||||
// Populate resources panel
|
||||
var d = response.data;
|
||||
$("#vf-res-memory").text(d.memory || "-");
|
||||
$("#vf-res-cpu").text(d.cpu || "-");
|
||||
$("#vf-res-storage").text(d.storage || "-");
|
||||
|
||||
var trafficUsed = d.trafficUsedRaw || 0;
|
||||
var trafficTotal = d.trafficRaw || 0;
|
||||
if (trafficTotal > 0) {
|
||||
$("#vf-res-traffic").text(trafficUsed + " / " + trafficTotal + " GB");
|
||||
var pct = Math.min(100, Math.round((trafficUsed / trafficTotal) * 100));
|
||||
$("#vf-res-traffic-bar").css("width", pct + "%");
|
||||
if (pct > 90) {
|
||||
$("#vf-res-traffic-bar").addClass("bg-danger");
|
||||
} else if (pct > 70) {
|
||||
$("#vf-res-traffic-bar").addClass("bg-warning");
|
||||
}
|
||||
} else {
|
||||
$("#vf-res-traffic").text(d.traffic || "Unlimited");
|
||||
$("#vf-res-traffic-bar").css("width", "0%");
|
||||
}
|
||||
|
||||
var speedIn = d.networkSpeedInboundRaw || 0;
|
||||
var speedOut = d.networkSpeedOutboundRaw || 0;
|
||||
if (speedIn > 0 || speedOut > 0) {
|
||||
$("#vf-res-network-speed").text(speedIn + " / " + speedOut + " Mbps");
|
||||
} else {
|
||||
$("#vf-res-network-speed").text("-");
|
||||
}
|
||||
|
||||
$("#vf-resources-panel").show();
|
||||
|
||||
$("#vf-server-info").show();
|
||||
} else {
|
||||
$("#vf-server-info-error").show();
|
||||
@@ -260,77 +297,6 @@ 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
|
||||
// =========================================================================
|
||||
@@ -507,3 +473,119 @@ function vfOpenVnc(serviceId, systemUrl) {
|
||||
btn.prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Self Service — Credit & Usage
|
||||
// =========================================================================
|
||||
|
||||
function vfLoadSelfServiceUsage(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=selfServiceUsage"
|
||||
}).done(function (response) {
|
||||
if (response.success && response.data) {
|
||||
var data = response.data.data || response.data;
|
||||
|
||||
// Credit balance
|
||||
var balance = "-";
|
||||
if (data.credit !== undefined) {
|
||||
balance = parseFloat(data.credit).toFixed(2);
|
||||
} else if (data.balance !== undefined) {
|
||||
balance = parseFloat(data.balance).toFixed(2);
|
||||
}
|
||||
$("#vf-ss-credit-balance").text(balance);
|
||||
|
||||
// Usage breakdown
|
||||
var tbody = $("#vf-ss-usage-table");
|
||||
tbody.empty();
|
||||
|
||||
var items = data.usage || data.items || [];
|
||||
if (Array.isArray(items) && items.length > 0) {
|
||||
$.each(items, function (i, item) {
|
||||
var desc = item.description || item.name || item.server || "Item";
|
||||
var cost = item.cost !== undefined ? parseFloat(item.cost).toFixed(2) : "-";
|
||||
tbody.append('<tr><td>' + $('<span>').text(desc).html() + '</td><td class="text-right">' + $('<span>').text(cost).html() + '</td></tr>');
|
||||
});
|
||||
} else {
|
||||
tbody.append('<tr><td colspan="2" class="text-muted">No usage data available</td></tr>');
|
||||
}
|
||||
|
||||
$("#vf-selfservice-content").show();
|
||||
$("#vf-selfservice-panel").show();
|
||||
}
|
||||
}).fail(function () {
|
||||
// Self-service not available — keep panel hidden
|
||||
}).always(function () {
|
||||
$("#vf-selfservice-loader").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function vfLoadSelfServiceReport(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=selfServiceReport"
|
||||
}).done(function (response) {
|
||||
if (response.success && response.data) {
|
||||
var data = response.data.data || response.data;
|
||||
var tbody = $("#vf-ss-usage-table");
|
||||
tbody.empty();
|
||||
|
||||
var items = data.items || data.report || [];
|
||||
if (Array.isArray(items) && items.length > 0) {
|
||||
$.each(items, function (i, item) {
|
||||
var desc = item.description || item.name || "Item";
|
||||
var cost = item.cost !== undefined ? parseFloat(item.cost).toFixed(2) : "-";
|
||||
tbody.append('<tr><td>' + $('<span>').text(desc).html() + '</td><td class="text-right">' + $('<span>').text(cost).html() + '</td></tr>');
|
||||
});
|
||||
} else {
|
||||
tbody.append('<tr><td colspan="2" class="text-muted">No report data available</td></tr>');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function vfAddCredit(serviceId, systemUrl) {
|
||||
var amount = $("#vf-ss-credit-amount").val();
|
||||
var alertDiv = $("#vf-selfservice-alert");
|
||||
var btn = $("#vf-ss-add-credit-btn");
|
||||
var spinner = $("#vf-ss-add-credit-spinner");
|
||||
|
||||
if (!amount || parseFloat(amount) <= 0) {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("Please enter a valid positive amount.");
|
||||
alertDiv.show();
|
||||
return;
|
||||
}
|
||||
|
||||
btn.prop("disabled", true);
|
||||
spinner.show();
|
||||
alertDiv.hide();
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=selfServiceAddCredit&tokens=" + encodeURIComponent(amount)
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text("Credit added successfully.");
|
||||
alertDiv.show();
|
||||
$("#vf-ss-credit-amount").val("");
|
||||
// Refresh usage data
|
||||
vfLoadSelfServiceUsage(serviceId, systemUrl);
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "Failed to add credit.");
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,44 +168,6 @@
|
||||
</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">
|
||||
@@ -240,8 +202,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* VNC Console Panel *}
|
||||
<div class="panel card panel-default mb-3">
|
||||
{* Resources Panel — populated by JS after server data loads *}
|
||||
<div id="vf-resources-panel" class="panel card panel-default mb-3" style="display: none;">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">Resources</h3>
|
||||
</div>
|
||||
<div class="panel-body card-body p-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="vf-resource-item mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="vf-bold">Memory</span>
|
||||
<span id="vf-res-memory"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vf-resource-item mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="vf-bold">CPU Cores</span>
|
||||
<span id="vf-res-cpu"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vf-resource-item mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="vf-bold">Storage</span>
|
||||
<span id="vf-res-storage"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="vf-resource-item mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="vf-bold">Traffic</span>
|
||||
<span id="vf-res-traffic"></span>
|
||||
</div>
|
||||
<div class="progress" style="height: 8px;">
|
||||
<div id="vf-res-traffic-bar" class="progress-bar" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vf-resource-item mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="vf-bold">Network Speed</span>
|
||||
<span id="vf-res-network-speed"></span>
|
||||
</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>
|
||||
|
||||
{* VNC Console Panel — hidden by default, shown by JS if VNC is enabled *}
|
||||
<div id="vf-vnc-panel" class="panel card panel-default mb-3" style="display: none;">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">VNC Console</h3>
|
||||
</div>
|
||||
@@ -255,6 +266,54 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{* Self Service — Billing & Usage Panel *}
|
||||
<div id="vf-selfservice-panel" class="panel card panel-default mb-3" style="display: none;">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">Billing & Usage</h3>
|
||||
</div>
|
||||
<div class="panel-body card-body p-4">
|
||||
<div id="vf-selfservice-alert" class="alert" style="display: none;"></div>
|
||||
<div id="vf-selfservice-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-selfservice-content" style="display: none;">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<h5 class="vf-bold">Credit Balance</h5>
|
||||
<div class="h4 mb-3" id="vf-ss-credit-balance">-</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5 class="vf-bold">Add Credit</h5>
|
||||
<div class="input-group mb-2">
|
||||
<input type="number" id="vf-ss-credit-amount" class="form-control" placeholder="Amount" min="1" step="1">
|
||||
<div class="input-group-append">
|
||||
<button id="vf-ss-add-credit-btn" onclick="vfAddCredit('{$serviceid}','{$systemURL}')" type="button" class="btn btn-primary">
|
||||
<span id="vf-ss-add-credit-spinner" class="spinner-border spinner-border-sm" style="display:none;"></span>
|
||||
Add Credit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="vf-bold">Usage Breakdown</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vf-ss-usage-table">
|
||||
<tr><td colspan="2" class="text-muted">Loading...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<script>vfLoadSelfServiceUsage('{$serviceid}', '{$systemURL}');</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{elseif $serviceStatus eq 'Suspended'}
|
||||
|
||||
<div class="panel card panel-default mb-3">
|
||||
|
||||
Reference in New Issue
Block a user