chore(release): 1.5.0
Some checks failed
Publish Release / release (push) Failing after 16s

Major client-area overhaul, WHMCS 9 + VirtFusion v7 compatibility, and a
hardening pass on every destructive client.php endpoint.

Tested against WHMCS 9.0.3 + VirtFusion v7.0.0 Build 9.

Features
- "On This Page" jump-link group injected into the WHMCS Actions sidebar
  via ClientAreaPrimarySidebar; auto-hides links for hidden panels.
- Monthly traffic chart (last 12 months) with rx/tx bars and centered
  legend; replaces the dead canvas that read non-existent JSON paths.
- Live Stats panel: CPU, memory, disk I/O from remoteState; 30s refresh
  while the panel is visible AND the page has focus.
- Filesystem usage rows in the Resources panel from qemu-guest-agent
  fsinfo; pseudo-FS filtered out.
- Server Overview meta chips: data-center location with country flag,
  OS template/agent name with kernel on hover, "Created N days ago".
- Hypervisor maintenance banner at the top of the page.
- Mask Sensitive screenshot mode: IPv4 keeps first two octets, IPv6
  keeps first two hextets, hostnames keep first char per dot-label.
  Inputs masked via text-security: disc; covers Server Name + Hostname
  + IP cells + rDNS panel rows.
- Per-IP copy buttons folded into the Server Overview cells (replaces
  the deleted standalone Network panel).
- VNC viewer popup served from a same-origin authenticated route
  (client.php?action=vncViewer) — POST + requireSameOrigin, rotates
  the wss token on every open, X-Frame-Options DENY, strict CSP.

Bug Fixes
- UsageUpdate cron silently no-op'd: read server.usage.traffic.used
  which doesn't exist. Bandwidth now from /servers/{id}/traffic;
  disk usage from remoteState.agent.fsinfo.
- WHMCS 9 multi-service order short-circuit: AfterModuleCreate's
  AcceptOrder fired after the first service and terminated the batch
  loop, orphaning siblings. Defer until every VF service in the order
  has a server_id.
- Orphaned services produced six generic 500s; new
  requireProvisionedService() helper emits one clean 409 with an
  actionable message. Wired into all 17 client.php cases.
- Server Overview Traffic showed "- / Unlimited"; now renders real
  bytes and "Unmetered" (limit=0 is per-period uncapped, not feature-off).
- Rename endpoint moved to PUT /servers/{id}/modify/name in VF v7
  (was 404'ing); response is HTTP 201 not 200/204.
- Rename was force-lowercasing the input; relaxed validation to
  preserve case + freeze the input row mid-flight to prevent
  double-submits.
- "Other" OS category icon override removed; uses VirtFusion's icon
  instead of a hardcoded SVG.
- Save button squish on the rename row fixed via flex-wrap layout.

Security
- CSRF protection (requirePost + requireSameOrigin) added to every
  destructive POST: rebuild, resetPassword, resetServerPassword,
  powerAction, rename, selfServiceAddCredit, toggleVnc, vncViewer.
  Previously only rdnsUpdate had it.
- Open-redirect defence in Module::fetchLoginTokens — refuses to
  return a redirect URL whose host doesn't match the configured VF
  panel hostname.
- Per-action rate limiting via new Module::requireRateLimit helper
  (Cache-backed): rebuild 60s, resetPassword/resetServerPassword 30s,
  powerAction 10s, vncViewer/toggleVnc/selfServiceAddCredit 5s.
- vncViewer route delivers strict Content-Security-Policy
  (default-src none, script-src self + VF panel, connect-src wss VF
  panel, frame-ancestors none).
- IPv6 examples in placeholder/comments switched to the IANA
  documentation prefix 2001:db8::/32 (RFC 3849).

Removed
- Network panel (duplicated Server Overview IP rows).
- VNC enable/disable toggle (VF firewall flag is non-functional;
  toggle was misleading).
- Network Speed row in Resources panel (always 0 from VF API).

Internal
- Module::fetchServerData now passes ?remoteState=true.
- ServerResource::process exposes osName/osPretty/osKernel/osDistro/
  osIcon/location/locationIcon/hypervisorMaintenance/createdAt/
  builtAt/live.* fields.
- Module::toggleVnc corrected to send {vnc:bool} (the actual API
  param) instead of {enabled:bool} (silent no-op).
- Module::getVncConsole + toggleVnc return baseUrl alongside the
  envelope so the viewer route can build the wss URL.
- Panel margins tightened mb-3 → mb-2 across all 11 panels.
This commit is contained in:
Prophet731
2026-04-28 22:07:27 -04:00
parent 7825f6be80
commit 27cbe40c52
11 changed files with 1873 additions and 363 deletions

View File

@@ -2,6 +2,78 @@
All notable changes to the VirtFusion Direct Provisioning Module for WHMCS.
## [1.5.0] - 2026-04-28
> **Tested against:** WHMCS 9.0.3 and VirtFusion v7.0.0 Build 9.
### Features
- **In-page section navigation in the WHMCS Actions sidebar.** Customers viewing a VirtFusion service get an "On This Page" group in the sidebar with jump-links to every visible panel (Overview, Traffic, Live Stats, Power, Manage, Rebuild, Reverse DNS, Resources, Billing & Usage, Billing Overview). Rendered server-side via a `ClientAreaPrimarySidebar` hook that's gated to productdetails pages for VF services only — no impact on other modules. Each link smooth-scrolls to its target via a delegated click handler. Conditionally-shown panels (Live Stats when remoteState is unavailable, Reverse DNS when PowerDNS isn't configured, Resources/Self-Service before their data loads) auto-hide their corresponding sidebar links so customers don't see dead jumps. VNC is intentionally excluded — its panel is the first thing on the page already. Theme-agnostic — WHMCS handles sidebar rendering for Six, Twenty-One, Lagom, etc.
- **Traffic chart panel showing the last 12 months of monthly aggregates.** New panel between Server Overview and Power Management, sourced from `/servers/{id}/traffic`'s `monthly[]` array. Renders side-by-side rx (blue) and tx (green) bars per month with month-or-month/year labels at the bottom and a centered legend with breathing room. Current period's used/limit/remaining tile sits below the chart. Replaces the previously broken in-resources chart that read non-existent `data.entries` / `data.used` paths and silently never rendered for any service since it was first added.
- **Live Stats panel — CPU, memory, disk I/O with 30s auto-refresh.** Surfaces `remoteState.cpu`, `remoteState.memory.{actual,unused}`, and `remoteState.disk.vda.{rd.bytes,wr.bytes}` from VirtFusion's libvirt introspection. CPU and memory render as colored progress bars with `bg-warning` at 75% and `bg-danger` at 90%. Disk I/O shows cumulative bytes since boot. Refresh polls `serverData` every 30s while the panel is visible AND the page has focus — pauses on `visibilitychange` to avoid hammering libvirt when the customer alt-tabs away. Hidden entirely when the upstream call returns no remoteState block.
- **Filesystem usage in the Resources panel.** Per-mount usage rows sourced from qemu-guest-agent's fsinfo block (requires `qemu-guest-agent` installed on the VM). Pseudo-FS (proc/sysfs/devtmpfs/tmpfs/cgroup/etc.) plus `/boot*` and `/run*` are filtered out — only customer-meaningful mounts show. Each row has a progress bar with the same 75%/90% warning thresholds. Hidden when the agent isn't running, with a one-line hint that customers can install qemu-guest-agent to populate the section.
- **Server Overview meta bar — Location, OS, lifetime.** Top of the panel now shows three chips: data-center location with country flag emoji (from `hypervisor.group.{name,icon}`, country codes mapped via Regional Indicator Symbols), the OS template name preferring the qemu-agent's pretty name when available (with kernel version on hover), and a relative "Created N days ago" chip with the absolute timestamp on hover.
- **Hypervisor maintenance banner.** Yellow alert at the very top of the page when `hypervisor.maintenance=true`. Prepares the customer to expect "operation may be unavailable" errors so they don't open support tickets for what's actually known maintenance.
- **"Mask Sensitive" toggle for screenshot-safe viewing.** Button on the Server Overview meta bar. When active, masks IPv4 (keeps first two octets — `205.186.•••.•••`), IPv6 (keeps first two hextets — `2602:2f3:••••::•`), hostnames (keeps first char of each dot-separated label — `m•••.e••••••.c••`). Covers the Server Name input, Hostname row, IPv4/IPv6 cells in Server Overview, AND in the Reverse DNS panel rows. Input fields mask via CSS `text-security: disc` (preserves the underlying value for editing — focus reveals); text cells via attribute swap with the original cached on `data-vf-ip-original`. State persists in `sessionStorage` so screenshots taken across page refreshes stay masked.
- **Per-IP copy buttons in Server Overview cells.** Each IPv4 / IPv6 address renders as a row with a copy button (replaces the standalone Network panel that duplicated this information).
### Bug Fixes
- **Critical: UsageUpdate cron silently never recorded any usage.** `VirtFusionDirect_UsageUpdate()` was reading `server.usage.traffic.used` and `server.usage.storage.used` — neither path exists in any VirtFusion API response, so `tblhosting.bwused` and `diskused` stayed at 0 forever for every service. Bandwidth now sourced from `/servers/{id}/traffic`'s `monthly[0].total` (canonical billing-period bytes); disk usage from `/servers/{id}?remoteState=true`'s `agent.fsinfo[]` summed (best-effort — only updates when qemu-guest-agent is running). Limits (`settings.resources.{storage,traffic}`) were already correct and untouched. Verified on live data: usage columns now populate correctly, unblocking suspend-on-overage rules and customer-facing usage bars.
- **Critical: WHMCS 9 multi-service order short-circuit.** WHMCS 9 added a batch order-acceptance loop that terminates as soon as the order leaves Pending status — calling `localAPI('AcceptOrder')` mid-batch (which our `AfterModuleCreate` hook did to advance the order) caused subsequent VF services in the same order to be skipped, leaving them Active in `tblhosting` but with no `mod_virtfusion_direct` row and no actual VPS in VirtFusion. Fix: defer the auto-accept until every VF service in the order has been provisioned. The hook fires once per service, so the last one to complete sees no unprovisioned siblings and triggers the actual accept. WHMCS 8 was unaffected (its loop ignored order status mid-batch); deferring there is harmless.
- **Service detail page returned generic 500s for orphaned services.** When a service exists in `tblhosting` but has no row (or NULL `server_id`) in `mod_virtfusion_direct` — e.g., the multi-service order bug above, or a failed CreateAccount — every client.php endpoint silently bailed via `resolveServiceContext()` and the customer saw a string of generic 500s with no actionable message. Added `Module::requireProvisionedService()` helper that emits a single clean 409 ("Server has not been provisioned yet. Please contact support if this is unexpected.") on the first endpoint call. Wired into all 17 client.php cases after `validateUserOwnsService()` so customers see one clear message instead of six broken sections.
- **Traffic display in Server Overview showed `- / Unlimited` even for servers with measured usage.** Same root cause as the UsageUpdate bug: `ServerResource::process()` read the non-existent `usage.traffic.used` path. Fixed by having `Module::fetchServerData()` make a secondary call to `/servers/{id}/traffic` and merge the current period's total bytes onto the server object as `trafficUsedBytes`; ServerResource reads from that stable field. Also renamed "Unlimited" → "Unmetered" everywhere — limit=0 means no cap, but traffic is still tracked per period, so Unmetered is more accurate.
- **VNC viewer popup didn't render — wrong URL pattern.** The `/vnc/?token=...` URL VirtFusion returns is the raw WebSocket endpoint and rejects HTTP GET with 405. The actual noVNC viewer is a tiny HTML shell that loads `<vfBaseUrl>/vnc/vnc.js`. Fixed by serving the shell ourselves from a same-origin authenticated PHP route (`client.php?action=vncViewer`) — see the Security section below for the full shape.
- **VNC toggle was lying and was removed.** The PHP `Module::toggleVnc()` was POSTing `{enabled: bool}` to VirtFusion when the API parameter is actually `vnc: bool` — silent no-op. Even after fixing the param, the API's `vnc.enabled` response field stayed `false` regardless of toggle state, and the wss endpoint accepts connections regardless of the panel toggle (per VirtFusion's current implementation, the toggle only manages a firewall flag that's currently broken at their panel level). Toggle removed entirely; VNC is treated as always-available, gated by WHMCS session + service ownership at our layer.
- **OS gallery's "Other" category icon was hardcoded to a generic SVG.** Forced override in `Module::groupOsTemplates()` and matching branches in `module.js` and `hooks.php` were nulling out the icon VirtFusion provides for the category. Reverted to use the upstream icon — applies in both the client-area Rebuild gallery and the checkout-side OS picker. Singleton-collected templates (those merged into a synthetic "Other" bucket) inherit the icon from VF's "Other" category if one was present in the source data.
- **Save button squished on the Server Name rename row.** Single flex row with 6px gap and a 200px max-width input was packing the randomise + save buttons into too-narrow columns on mid-width viewports. Wrapped the inputs in a flex-wrap container with explicit min-widths so all three controls stay readable down to mobile.
- **Server rename was force-lowercasing the input.** Customer typed "VPS-01", got "vps-01" back. Both client and server validation enforced an RFC-1123-style hostname regex, but VirtFusion's `name` field is a display label that accepts any printable string up to 63 chars. Validation relaxed to non-empty + length cap + reject control characters. Mid-flight, the input field, Save, and Randomise buttons all freeze together so customers can't double-submit or edit while the rename is in flight.
- **Server rename hit the wrong VirtFusion endpoint** (and silently treated success as failure). The PHP module was using `PATCH /servers/{id}/name`, which VirtFusion v7 returns 404 for — the path moved to `PUT /servers/{id}/modify/name` (consistent with `/modify/memory`, `/modify/cpu`, etc.). Even after fixing the URL, success was treated as failure because v7 returns HTTP 201 (Created) on rename and our success whitelist only accepted 200/204. Fixed both: switched to PUT + new path, added 201 to the whitelist.
### Security
- **CSRF protection added to every destructive client.php action.** `rebuild`, `resetPassword`, `resetServerPassword`, `powerAction`, `rename`, `selfServiceAddCredit`, `toggleVnc`, and `vncViewer` now require `requirePost()` + `requireSameOrigin()`. Closes a class of attacks where a malicious page (or compromised ad slot) could embed a hidden form targeting `client.php?serviceID=X&action=rebuild` and destroy the customer's data on the next page load they made while logged in. Previously only `rdnsUpdate` carried these gates.
- **Open-redirect defence on SSO.** `Module::fetchLoginTokens()` now validates that the URL constructed from VirtFusion's `endpoint_complete` resolves to the same hostname as the configured VirtFusion panel before returning it for redirect. Defends against a hostname-mismatched response (compromised VF panel, tampered `tblservers` row, etc.) being used to phish customers.
- **VNC viewer popup served from a same-origin authenticated POST route.** Click → hidden form-submit (POST) to `client.php?action=vncViewer``requirePost()` + `requireSameOrigin()` + `isAuthenticated()` + `validateUserOwnsService()` + `requireProvisionedService()` → POST `/vnc {vnc:true}` to rotate the wss token → return `text/html` with the noVNC shell embedded. The wss URL never appears in any URL bar, browser history, or shareable link — it lives only in the HTTP response body delivered to a logged-in session. Each open rotates the token, so any prior credential exposure is short-lived. Response carries `X-Frame-Options: DENY`, `Cache-Control: no-store, private`, and a strict `Content-Security-Policy` (`default-src 'none'`, `script-src 'self' <vf-panel>`, `connect-src wss://<vf-panel> <vf-panel>`, `frame-ancestors 'none'`) so the viewer cannot be re-hosted, embedded, or opened in a way that loads scripts from anywhere outside our WHMCS host or the configured VirtFusion panel.
- **Per-action rate limiting** on destructive endpoints via the new `Module::requireRateLimit()` helper (Cache-backed, Redis-or-filesystem). Limits: `rebuild` 60 s, `resetPassword` / `resetServerPassword` 30 s, `powerAction` 10 s, `vncViewer` / `toggleVnc` / `selfServiceAddCredit` 5 s — all per-(action, serviceID). Defence against runaway browser scripts and accidental double-submits hammering the VirtFusion API. Returns 429 with a clear "Too many requests" message instead of letting the request through.
- **IP and hostname masking covers screenshot-sensitive cells.** See the Mask Sensitive feature above — reduces leakage when customers screen-share or attach screenshots for support.
- **IPv6 examples in the Reverse DNS placeholder + comment switched to the IANA documentation prefix `2001:db8::/32`** (RFC 3849), eliminating an inadvertent reference to a deployer-specific IPv6 block that previously appeared in customer-facing UI.
### Removed
- **Network panel** (full removal) — duplicated Server Overview's IP rows. Per-IP copy buttons moved into the Overview cells via `vfRenderIpCells()`.
- **Network Speed row** from the Resources panel — VirtFusion's `inAverage`/`inPeak`/`inBurst` and matching out-fields all return 0 in our setup (network speed isn't capped at the package level), so the row was always empty.
- **VNC enable/disable toggle** — see Bug Fixes above.
### Internal
- **`Module::fetchServerData()` now passes `?remoteState=true`** on the upstream call so the response includes live CPU/memory, disk I/O counters, and qemu-agent fsinfo. Adds one libvirt round-trip per page load on the hypervisor side; revisit caching if hypervisor load becomes a concern.
- **`ServerResource::process()`** exposes new fields: `osName`, `osPretty`, `osKernel`, `osDistro`, `osIcon`, `location`, `locationIcon`, `hypervisorMaintenance`, `createdAt`, `builtAt`, plus a nested `live.{state, cpu, memoryActualKB, memoryUnusedKB, memoryAvailableKB, memoryRssKB, diskRdBytes, diskWrBytes, filesystems[]}` block.
- **`Module::toggleVnc()`** corrected to post `{vnc: bool}` (the actual API parameter) instead of `{enabled: bool}` (silent no-op). Also returns `baseUrl` alongside the API envelope so the new vncViewer route can build the wss URL without re-deriving it.
- **`Module::getVncConsole()`** also returns `baseUrl` for the same reason.
- **Panel margins tightened** from `mb-3` (16px) to `mb-2` (8px) across all 11 client-area panels for a tighter visual rhythm post-additions.
## [1.4.4] - 2026-04-25
### Bug Fixes