Enhance VirtFusion WHMCS module with security fixes, new features, and improved UX
Security improvements: - Enable SSL/TLS certificate verification by default (was disabled, MITM risk) - Remove error_reporting(0) that silenced all errors - Add input sanitization on all user parameters (int casting, regex filtering) - Return proper HTTP status codes (401, 403, 400, 500) instead of always 200 - Add XSS protection with htmlspecialchars and encodeURIComponent - Add null checks on API response data before property access New features: - Power management: boot, shutdown, restart, and force power off controls - Server rebuild: reinstall with any available OS template from client area - Server rename: change server display name via PATCH API - OS template fetching: client-side endpoint for rebuild OS selection - TestConnection: validate API credentials from WHMCS server settings - ServiceSingleSignOn: native WHMCS SSO integration for VirtFusion panel - Server status badge: visual indicator of server state in overview - Traffic usage display: show bandwidth used vs allocated - Checkout validation: ShoppingCartValidateCheckout hook ensures OS selection Ordering process improvements: - Add default "Select Operating System" placeholder option - Add "No SSH Key (Optional)" default for SSH dropdown - Hide SSH key field/container when no keys available - Wrap hook in try/catch to prevent checkout page breakage - Sanitize template names with htmlspecialchars - Use JSON_HEX_* flags for safe script injection Theme compatibility: - Properly formatted Smarty templates with readable indentation - Dual panel/card CSS classes for Bootstrap 3/4/5 compatibility - Responsive power button layout with mobile breakpoint - Framework-agnostic HTML that works with Six, Twenty-One, Lagom, and custom themes - Suspended service state messaging Code quality: - Readable, unminified JavaScript with JSDoc header - Structured CSS with logical section organization - Improved error messages throughout all provisioning functions - Added PATCH method support to Curl wrapper - Added curl error capture on connection failures - Added connection and request timeouts (10s/30s) - Fixed memory conversion to check key name instead of display name Documentation: - Complete README rewrite with installation, configuration, and troubleshooting guides - API endpoint reference table - Configurable options mapping documentation - Theme override instructions - Security considerations section https://claude.ai/code/session_01TCsJ4WZCGuEX3zqh1tQ2zx
This commit is contained in:
@@ -1 +1,134 @@
|
||||
.vf-bold{font-weight:800}.vf-small{font-size:.9rem}.vf-button{font-size:.8rem;padding:.95rem 1.5rem;font-weight:600}.vf-button-small{font-size:.8rem;padding:.75rem 1.3rem;font-weight:500}.vf-spinner-margin{margin-right:7px}.vf-badge{font-size:.8rem;padding:.5rem .9rem;text-transform:uppercase;font-weight:800}.vf-badge-active{background-color:rgba(32,177,0,.12);color:#276900;border-radius:6px}.vf-badge-awaiting{background-color:rgba(177,89,0,.12);color:#692000;border-radius:6px}#vf-login-button-spinner{display:none}#vf-password-reset-button-spinner{display:none}#vf-password-reset-error{display:none}#vf-password-reset-success{display:none}#vf-login-error{display:none}#vf-server-info{display:none}#vf-server-info-error{display:none}#vf-server-info-loader{min-height:136px}#vf-loading{display:inline-block;width:30px;height:30px;border:3px solid rgba(225,224,224,.3);border-radius:50%;border-top-color:#0e151a;animation:vf-spin 1s ease-in-out infinite;-webkit-animation:vf-spin 1s ease-in-out infinite}.vf-loader{margin:30px}@keyframes vf-spin{to{transform:rotate(360deg)}}@-webkit-keyframes vf-spin{to{transform:rotate(360deg)}}#vf-server-info-error{margin:10px}
|
||||
/* VirtFusion Direct Provisioning Module Styles */
|
||||
|
||||
/* Typography */
|
||||
.vf-bold {
|
||||
font-weight: 800;
|
||||
}
|
||||
.vf-small {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.vf-button {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.95rem 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.vf-button-small {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.75rem 1.3rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.vf-spinner-margin {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.vf-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
}
|
||||
.vf-badge-active {
|
||||
background-color: rgba(32, 177, 0, 0.12);
|
||||
color: #276900;
|
||||
}
|
||||
.vf-badge-awaiting {
|
||||
background-color: rgba(177, 89, 0, 0.12);
|
||||
color: #692000;
|
||||
}
|
||||
.vf-badge-suspended {
|
||||
background-color: rgba(220, 53, 69, 0.12);
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Power Management */
|
||||
.vf-power-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.vf-btn-power {
|
||||
min-width: 100px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.5rem 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Hidden elements (initial state) */
|
||||
#vf-login-button-spinner {
|
||||
display: none;
|
||||
}
|
||||
#vf-password-reset-button-spinner {
|
||||
display: none;
|
||||
}
|
||||
#vf-password-reset-error {
|
||||
display: none;
|
||||
}
|
||||
#vf-password-reset-success {
|
||||
display: none;
|
||||
}
|
||||
#vf-login-error {
|
||||
display: none;
|
||||
}
|
||||
#vf-server-info {
|
||||
display: none;
|
||||
}
|
||||
#vf-server-info-error {
|
||||
display: none;
|
||||
}
|
||||
#vf-data-server-traffic-sep {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Loader */
|
||||
#vf-server-info-loader {
|
||||
min-height: 136px;
|
||||
}
|
||||
#vf-loading {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 3px solid rgba(225, 224, 224, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #0e151a;
|
||||
animation: vf-spin 1s ease-in-out infinite;
|
||||
-webkit-animation: vf-spin 1s ease-in-out infinite;
|
||||
}
|
||||
.vf-loader {
|
||||
margin: 30px;
|
||||
}
|
||||
|
||||
@keyframes vf-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes vf-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Error message spacing */
|
||||
#vf-server-info-error {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.vf-power-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
.vf-btn-power {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
<div class="alert alert-danger"><p>Oops! Something went wrong.</p></div><p>Please go back and try again.</p><p>If the problem persists, please contact support.</p>
|
||||
<div class="panel card panel-default mb-3">
|
||||
<div class="panel-heading card-header">
|
||||
<h3 class="panel-title card-title m-0">Error</h3>
|
||||
</div>
|
||||
<div class="panel-body card-body p-4">
|
||||
<div class="alert alert-danger mb-0">
|
||||
<p><strong>Something went wrong.</strong></p>
|
||||
<p class="mb-0">Please go back and try again. If the problem persists, please contact support.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1 +1,261 @@
|
||||
function vfServerData(e,r){$("#vf-server-info-error").hide(),$.ajax({type:"GET",dataType:"json",url:r+"modules/servers/VirtFusionDirect/client.php?serviceID="+e+"&action=serverData"}).done(function(e){e.success?($("#vf-data-server-name").text(e.data.name),$("#vf-data-server-hostname").text(e.data.hostname),$("#vf-data-server-memory").text(e.data.memory),$("#vf-data-server-traffic").text(e.data.traffic),$("#vf-data-server-storage").text(e.data.storage),$("#vf-data-server-cpu").text(e.data.cpu),$("#vf-data-server-ipv4").text(e.data.primaryNetwork.ipv4),$("#vf-data-server-ipv6").text(e.data.primaryNetwork.ipv6),$("#vf-server-info").show()):($("#vf-server-info-error").show(),$("#vf-server-info").hide())}).fail(function(e){}).always(function(e){$("#vf-server-info-loader-container").hide()})}function vfServerDataAdmin(e,r){$("#vf-loader").show(),$("#vf-server-info").hide(),$("#vf-server-info-error").hide(),$.ajax({type:"GET",dataType:"json",url:r+"modules/servers/VirtFusionDirect/admin.php?serviceID="+e+"&action=serverData"}).done(function(e){e.success?($("#vf-data-server-name").text(e.data.name),$("#vf-data-server-hostname").text(e.data.hostname),$("#vf-data-server-memory").text(e.data.memory),$("#vf-data-server-traffic").text(e.data.traffic),$("#vf-data-server-storage").text(e.data.storage),$("#vf-data-server-cpu").text(e.data.cpu),$("#vf-data-server-ipv4").text(e.data.primaryNetwork.ipv4),$("#vf-data-server-ipv6").text(e.data.primaryNetwork.ipv6),$("#vf-server-info").show()):($("#vf-server-info-error").show(),$("#vf-server-info-error-message").text(e.errors),$("#vf-server-info").hide())}).fail(function(e){}).always(function(e){$("#vf-loader").hide()})}function vfUserPasswordReset(e,r){$("#vf-password-reset-button-spinner").show(),$("#vf-password-reset-error").hide(),$("#vf-password-reset-success").hide(),$.ajax({type:"GET",dataType:"json",url:r+"modules/servers/VirtFusionDirect/client.php?serviceID="+e+"&action=resetPassword"}).done(function(e){e.success?($("#vf-password-reset-success").show(),$("#vf-data-user-email").text(e.data.email),$("#vf-data-user-password").text(e.data.password),console.log(e.data.email)):$("#vf-password-reset-error").show()}).fail(function(e){}).always(function(e){$("#vf-password-reset-button-spinner").hide()})}function vfLoginAsServerOwner(e,r,t=!0){vfLoginError(!1),$("#vf-login-button").prop("disabled",!0),$("#vf-login-button-spinner").show(),$.ajax({type:"GET",dataType:"json",url:r+"modules/servers/VirtFusionDirect/client.php?serviceID="+e+"&action=loginAsServerOwner"}).done(function(e){e.success?e.token_url&&(t?window.open(e.token_url):window.location.href=e.token_url):vfLoginError(!0)}).fail(function(e){vfLoginError(!0)}).always(function(e){$("#vf-login-button-spinner").hide(),$("#vf-login-button").prop("disabled",!1)})}function vfLoginError(e,r="Oops! Something went wrong. Try again later."){e?($("#vf-login-error").text(r),$("#vf-login-error").show()):$("#vf-login-error").hide()}function impersonateServerOwner(e,r){$.ajax({type:"GET",dataType:"json",url:r+"modules/servers/VirtFusionDirect/admin.php?serviceID="+e+"&action=impersonateServerOwner"}).done(function(e){e.success&&e.user&&window.open(e.url+"/_imp/in/"+e.user.id+"/-")}).fail(function(e){}).always(function(e){})}
|
||||
/**
|
||||
* VirtFusion Direct Provisioning Module - Client JavaScript
|
||||
*
|
||||
* Handles client-side interactions for server management including:
|
||||
* - Server data display
|
||||
* - Power management (boot, shutdown, restart, power off)
|
||||
* - Control panel login (SSO)
|
||||
* - Password reset
|
||||
* - Server rebuild
|
||||
* - OS template loading
|
||||
*/
|
||||
|
||||
function vfServerData(serviceId, systemUrl) {
|
||||
$("#vf-server-info-error").hide();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=serverData"
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
$("#vf-data-server-name").text(response.data.name);
|
||||
$("#vf-data-server-hostname").text(response.data.hostname);
|
||||
$("#vf-data-server-memory").text(response.data.memory);
|
||||
$("#vf-data-server-traffic").text(response.data.traffic);
|
||||
$("#vf-data-server-traffic-used").text(response.data.trafficUsed || "-");
|
||||
$("#vf-data-server-storage").text(response.data.storage);
|
||||
$("#vf-data-server-cpu").text(response.data.cpu);
|
||||
$("#vf-data-server-ipv4").text(response.data.primaryNetwork.ipv4);
|
||||
$("#vf-data-server-ipv6").text(response.data.primaryNetwork.ipv6);
|
||||
|
||||
// Update status badge
|
||||
var statusBadge = $("#vf-status-badge");
|
||||
var status = (response.data.status || "unknown").toLowerCase();
|
||||
statusBadge.text(status.charAt(0).toUpperCase() + status.slice(1));
|
||||
if (status === "active" || status === "running") {
|
||||
statusBadge.addClass("vf-badge-active");
|
||||
} else if (status === "suspended") {
|
||||
statusBadge.addClass("vf-badge-suspended");
|
||||
} else {
|
||||
statusBadge.addClass("vf-badge-awaiting");
|
||||
}
|
||||
|
||||
$("#vf-server-info").show();
|
||||
} else {
|
||||
$("#vf-server-info-error").show();
|
||||
$("#vf-server-info").hide();
|
||||
}
|
||||
}).fail(function () {
|
||||
$("#vf-server-info-error").show();
|
||||
}).always(function () {
|
||||
$("#vf-server-info-loader-container").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function vfServerDataAdmin(serviceId, systemUrl) {
|
||||
$("#vf-loader").show();
|
||||
$("#vf-server-info").hide();
|
||||
$("#vf-server-info-error").hide();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/admin.php?serviceID=" + encodeURIComponent(serviceId) + "&action=serverData"
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
$("#vf-data-server-name").text(response.data.name);
|
||||
$("#vf-data-server-hostname").text(response.data.hostname);
|
||||
$("#vf-data-server-memory").text(response.data.memory);
|
||||
$("#vf-data-server-traffic").text(response.data.traffic);
|
||||
$("#vf-data-server-storage").text(response.data.storage);
|
||||
$("#vf-data-server-cpu").text(response.data.cpu);
|
||||
$("#vf-data-server-ipv4").text(response.data.primaryNetwork.ipv4);
|
||||
$("#vf-data-server-ipv6").text(response.data.primaryNetwork.ipv6);
|
||||
$("#vf-server-info").show();
|
||||
} else {
|
||||
$("#vf-server-info-error").show();
|
||||
$("#vf-server-info-error-message").text(response.errors);
|
||||
$("#vf-server-info").hide();
|
||||
}
|
||||
}).fail(function () {
|
||||
$("#vf-server-info-error").show();
|
||||
}).always(function () {
|
||||
$("#vf-loader").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function vfUserPasswordReset(serviceId, systemUrl) {
|
||||
$("#vf-password-reset-button-spinner").show();
|
||||
$("#vf-password-reset-error").hide();
|
||||
$("#vf-password-reset-success").hide();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=resetPassword"
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
$("#vf-password-reset-success").show();
|
||||
$("#vf-data-user-email").text(response.data.email);
|
||||
$("#vf-data-user-password").text(response.data.password);
|
||||
} else {
|
||||
$("#vf-password-reset-error").show();
|
||||
}
|
||||
}).fail(function () {
|
||||
$("#vf-password-reset-error").show();
|
||||
}).always(function () {
|
||||
$("#vf-password-reset-button-spinner").hide();
|
||||
});
|
||||
}
|
||||
|
||||
function vfLoginAsServerOwner(serviceId, systemUrl, newWindow) {
|
||||
newWindow = newWindow !== false;
|
||||
vfLoginError(false);
|
||||
$("#vf-login-button").prop("disabled", true);
|
||||
$("#vf-login-button-spinner").show();
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=loginAsServerOwner"
|
||||
}).done(function (response) {
|
||||
if (response.success && response.token_url) {
|
||||
if (newWindow) {
|
||||
window.open(response.token_url);
|
||||
} else {
|
||||
window.location.href = response.token_url;
|
||||
}
|
||||
} else {
|
||||
vfLoginError(true);
|
||||
}
|
||||
}).fail(function () {
|
||||
vfLoginError(true);
|
||||
}).always(function () {
|
||||
$("#vf-login-button-spinner").hide();
|
||||
$("#vf-login-button").prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
function vfLoginError(show, message) {
|
||||
message = message || "Unable to open the control panel. Please try again later.";
|
||||
if (show) {
|
||||
$("#vf-login-error").text(message);
|
||||
$("#vf-login-error").show();
|
||||
} else {
|
||||
$("#vf-login-error").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function vfPowerAction(serviceId, systemUrl, action) {
|
||||
var btn = $("#vf-power-" + action);
|
||||
var spinner = btn.find(".vf-btn-spinner");
|
||||
var alertDiv = $("#vf-power-alert");
|
||||
|
||||
// Disable all power buttons during action
|
||||
$(".vf-btn-power").prop("disabled", true);
|
||||
spinner.show();
|
||||
alertDiv.hide();
|
||||
|
||||
var actionLabels = {
|
||||
boot: "Starting",
|
||||
shutdown: "Shutting down",
|
||||
restart: "Restarting",
|
||||
poweroff: "Forcing off"
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=powerAction&powerAction=" + encodeURIComponent(action)
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text(response.data.message || (actionLabels[action] + " server..."));
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "Power 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();
|
||||
$(".vf-btn-power").prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
function vfLoadOsTemplates(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=osTemplates"
|
||||
}).done(function (response) {
|
||||
var select = $("#vf-rebuild-os");
|
||||
select.empty();
|
||||
if (response.success && response.data && response.data.length > 0) {
|
||||
select.append('<option value="">-- Select Operating System --</option>');
|
||||
$.each(response.data, function (i, template) {
|
||||
select.append('<option value="' + template.id + '">' + $('<span>').text(template.name).html() + '</option>');
|
||||
});
|
||||
} else {
|
||||
select.append('<option value="">No templates available</option>');
|
||||
}
|
||||
}).fail(function () {
|
||||
var select = $("#vf-rebuild-os");
|
||||
select.empty();
|
||||
select.append('<option value="">Error loading templates</option>');
|
||||
});
|
||||
}
|
||||
|
||||
function vfRebuildServer(serviceId, systemUrl) {
|
||||
var osId = $("#vf-rebuild-os").val();
|
||||
var alertDiv = $("#vf-rebuild-alert");
|
||||
|
||||
if (!osId) {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("Please select an operating system.");
|
||||
alertDiv.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm("Are you sure you want to rebuild this server? ALL DATA WILL BE ERASED. This action cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#vf-rebuild-button").prop("disabled", true);
|
||||
$("#vf-rebuild-spinner").show();
|
||||
alertDiv.hide();
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/client.php?serviceID=" + encodeURIComponent(serviceId) + "&action=rebuild&osId=" + encodeURIComponent(osId)
|
||||
}).done(function (response) {
|
||||
if (response.success) {
|
||||
alertDiv.removeClass("alert-danger").addClass("alert-success");
|
||||
alertDiv.text(response.data.message || "Server rebuild initiated. You will receive an email when the process is complete.");
|
||||
} else {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text(response.errors || "Rebuild failed.");
|
||||
}
|
||||
alertDiv.show();
|
||||
}).fail(function () {
|
||||
alertDiv.removeClass("alert-success").addClass("alert-danger");
|
||||
alertDiv.text("An error occurred. Please try again.");
|
||||
alertDiv.show();
|
||||
}).always(function () {
|
||||
$("#vf-rebuild-spinner").hide();
|
||||
$("#vf-rebuild-button").prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
function impersonateServerOwner(serviceId, systemUrl) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: systemUrl + "modules/servers/VirtFusionDirect/admin.php?serviceID=" + encodeURIComponent(serviceId) + "&action=impersonateServerOwner"
|
||||
}).done(function (response) {
|
||||
if (response.success && response.user) {
|
||||
window.open(response.url + "/_imp/in/" + response.user.id + "/-");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user