12 Commits

Author SHA1 Message Date
Andrew
9aa8378599 Merge pull request #2 from EZSCALE/Prophet731-patch-1
Update hooks.php
2024-01-16 12:13:15 -05:00
Andrew
f0c28a4961 Update hooks.php
Hide the SSH key field if the user isn't logged in to see them or is a new user.
2024-01-16 12:11:26 -05:00
Prophet731
a46223e5ac Update issue templates 2023-09-11 00:31:03 -04:00
98250f2f4c Ahh typos 2023-09-10 23:58:17 -04:00
2691013cc4 Put the on in the wrong spot. 2023-09-10 23:57:00 -04:00
24e79eed31 Forgot to remove this 2023-09-10 23:50:54 -04:00
db3afee99e Deleted linter as its not really needed. 2023-09-10 23:50:36 -04:00
fb0cffc844 Changed to not required. 2023-09-10 23:49:53 -04:00
c6012fa63c Updated readme.me 2023-09-10 23:46:43 -04:00
b25530f063 Successful build of server. Ready for testing. 2023-09-10 23:32:59 -04:00
bb2e8ac538 Check the database first to see if the ID has been set, otherwise, fallback to querying the API based on product name. 2023-09-10 20:56:53 -04:00
07f3c69977 Added changes done by BlinkohHost.
See https://github.com/BlinkohHost/virtfusion-whmcs-module
2023-09-10 20:36:22 -04:00
9 changed files with 349 additions and 107 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,22 +0,0 @@
---
name: Lint Code Base
on: [ workflow_call ]
jobs:
build:
name: Lint Code Base
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
statuses: write
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Lint Code Base
uses: super-linter/super-linter@v5
env:
VALIDATE_ALL_CODEBASE: false
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@@ -5,11 +5,8 @@ on:
branches: branches:
- main - main
jobs: jobs:
linter:
uses: ./.github/workflows/linter.yml
publish-release: publish-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: linter
steps: steps:
- name: Publish Release - name: Publish Release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1

View File

@@ -5,19 +5,85 @@
![GitHub issues](https://img.shields.io/github/issues/EZSCALE/virtfusion-whmcs-module) ![GitHub issues](https://img.shields.io/github/issues/EZSCALE/virtfusion-whmcs-module)
![GitHub pull requests](https://img.shields.io/github/issues-pr/EZSCALE/virtfusion-whmcs-module) ![GitHub pull requests](https://img.shields.io/github/issues-pr/EZSCALE/virtfusion-whmcs-module)
This module requires VirtFusion v1.7.3 or higher as this is what it's based on. Please refer to the official [documenataion](https://docs.virtfusion.com/integrations/whmcs). This module requires VirtFusion v1.7.3 or higher as this is what it's based on. Please refer to the
official [documentation](https://docs.virtfusion.com/integrations/whmcs).
## Installation ## Installation
1. Download the latest release from the [releases](https://github.com/EZSCALE/virtfusion-whmcs-module/releases) page. 1. Download the latest release from the [releases](https://github.com/EZSCALE/virtfusion-whmcs-module/releases) page.
2. Extract the contents of the archive and upload the modules folder to your WHMCS installation directory. 2. Extract the contents of the archive and upload the modules folder to your WHMCS installation directory.
## :heavy_exclamation_mark: Important Notes :heavy_exclamation_mark:
You must create two custom fields in WHMCS for this module to work. You need to configure the following custom fields on
each product you want to use this module with.
| Field Name | Field Type | Description | Validation | Select Options | Admin Only | Required Field | Show on Order Form | Show on Invoice |
|--------------------------|------------|--------------------------|-------------|----------------|------------|----------------|--------------------|-----------------|
| Initial Operating System | Text Box | Set to whatever you want | Leave Blank | Leave Blank | :x: | :x: | :white_check_mark: | :x: |
| Initial SSH Key | Text Box | Set to whatever you want | Leave Blank | Leave Blank | :x: | :x: | :white_check_mark: | :x: |
You can run this SQL query to create the custom fields:
```sql
-- Insert records for Initial Operating System if they don't already exist
INSERT INTO tblcustomfields
(type, relid, fieldname, fieldtype, description, fieldoptions, regexpr, adminonly, required, showorder, showinvoice,
sortorder, created_at, updated_at)
SELECT 'product',
id,
'Initial Operating System',
'text',
'',
'',
'',
'',
'',
'on',
'',
0,
UTC_TIMESTAMP(),
UTC_TIMESTAMP()
FROM tblproducts
WHERE servertype = 'VirtFusionDirect'
AND NOT EXISTS (SELECT 1
FROM tblcustomfields
WHERE fieldname = 'Initial Operating System'
AND relid = tblproducts.id);
-- Insert records for Initial SSH Key if they don't already exist
INSERT INTO tblcustomfields
(type, relid, fieldname, fieldtype, description, fieldoptions, regexpr, adminonly, required, showorder, showinvoice,
sortorder, created_at, updated_at)
SELECT 'product',
id,
'Initial SSH Key',
'text',
'',
'',
'',
'',
'',
'on',
'',
0,
UTC_TIMESTAMP(),
UTC_TIMESTAMP()
FROM tblproducts
WHERE servertype = 'VirtFusionDirect'
AND NOT EXISTS (SELECT 1
FROM tblcustomfields
WHERE fieldname = 'Initial SSH Key'
AND relid = tblproducts.id);
```
## What does this module change? ## What does this module change?
This module changes the following things: This module changes the following things:
- Adds configurable options to the product configuration page to allow the user to select the operating system and add - Adds configurable options to the product configuration page to allow the user to select the operating system and add
a ssh key to the initial deployment. an ssh key to the initial deployment.
## TODO ## TODO

View File

@@ -83,9 +83,16 @@ function VirtFusionDirect_updateServerObject(array $params)
return (new ModuleFunctions())->updateServerObject($params); return (new ModuleFunctions())->updateServerObject($params);
} }
/**
* Allows changing of the package of a server
*
* @author https://github.com/BlinkohHost/virtfusion-whmcs-module
* @param array $params
* @return string
*/
function VirtFusionDirect_ChangePackage(array $params) function VirtFusionDirect_ChangePackage(array $params)
{ {
return 'success'; return (new ModuleFunctions())->changePackage($params);
} }
function VirtFusionDirect_AdminServicesTabFields(array $params) function VirtFusionDirect_AdminServicesTabFields(array $params)

View File

@@ -7,14 +7,7 @@ if (!defined("WHMCS")) {
die("This file cannot be accessed directly"); die("This file cannot be accessed directly");
} }
if (!function_exists('add_hook_os_templates')) { add_hook('ClientAreaFooterOutput', 1, function ($vars) {
/**
* @param array $vars
* @return array|null
* @throws JsonException
*/
function add_hook_os_templates(array $vars): ?array
{
if (!isset($vars['productinfo']['module']) || $vars['productinfo']['module'] !== 'VirtFusionDirect') { if (!isset($vars['productinfo']['module']) || $vars['productinfo']['module'] !== 'VirtFusionDirect') {
return null; return null;
} }
@@ -22,7 +15,7 @@ if (!function_exists('add_hook_os_templates')) {
$cs = new ConfigureService(); $cs = new ConfigureService();
$templates_data = $cs->fetchTemplates( $templates_data = $cs->fetchTemplates(
$cs->fetchPackageId($vars['productinfo']['name']) $cs->fetchPackageByDbId($vars['productinfo']['pid']) ?? $cs->fetchPackageId($vars['productinfo']['name'])
); );
if (empty($templates_data)) { if (empty($templates_data)) {
@@ -44,21 +37,8 @@ if (!function_exists('add_hook_os_templates')) {
return strcmp($a['name'], $b['name']); return strcmp($a['name'], $b['name']);
}); });
$osTemplates = [
'id' => 'os_template',
'optionname' => 'Initial Operating System',
'optiontype' => 1,
'options' => $dropdownOptions,
'selectedvalue' => ''
];
$sshKeys = $cs->getUserSshKeys($vars['loggedinuser']); $sshKeys = $cs->getUserSshKeys($vars['loggedinuser']);
$sshKeysOptions = array_map(function ($sshKey) {
$sshKeysOptions = [
'id' => 'ssh_key',
'optionname' => 'Initial SSH Key',
'optiontype' => 1,
'options' => array_map(function ($sshKey) {
if ($sshKey['enabled'] === false) { if ($sshKey['enabled'] === false) {
return null; return null;
} }
@@ -67,22 +47,80 @@ if (!function_exists('add_hook_os_templates')) {
'id' => $sshKey['id'], 'id' => $sshKey['id'],
'name' => $sshKey['name'] 'name' => $sshKey['name']
]; ];
}, $sshKeys['data'] ?? []), }, $sshKeys['data'] ?? []);
'selectedvalue' => ''
];
$configurableoptions = $vars['configurableoptions']; $osID = array_values(array_filter(array_map(function ($option) {
if ($option['textid'] === 'initialoperatingsystem') {
array_push( return $option['id'];
$configurableoptions,
$osTemplates,
$sshKeysOptions
);
return [
'configurableoptions' => $configurableoptions,
];
} }
}, $vars['customfields'])));
add_hook('ClientAreaPageCart', 1, 'add_hook_os_templates'); $sshID = array_values(array_filter(array_map(function ($option) {
if ($option['textid'] === 'initialsshkey') {
return $option['id'];
} }
}, $vars['customfields'])));
// Construct the JavaScript code
return "
<script>
document.addEventListener('DOMContentLoaded', function() {
let osTemplates = ".json_encode($dropdownOptions, JSON_THROW_ON_ERROR).";
let sshKeys = ".json_encode($sshKeysOptions, JSON_THROW_ON_ERROR).";
const osInputField = document.querySelector('[name=\"customfield[".($osID[0] ?? null)."]\"]');
const osInputLabel = document.querySelector('[for=\"customfield".($osID[0] ?? null)."\"]');
const sshInputField = document.querySelector('[name=\"customfield[".($sshID[0] ?? null)."]\"]');
const sshInputLabel = document.querySelector('[for=\"customfield".($sshID[0] ?? null)."\"]');
// Create dropdown options menu, then add it to the DOM then on change, update the regular input.
let osSelect = document.createElement('select');
osSelect.className = 'form-control';
osTemplates.forEach(function(template) {
let option = document.createElement('option');
option.value = template.id;
option.text = template.name;
osSelect.appendChild(option);
});
// Set the default value of the input field to the first option in the dropdown.
osInputField.value = osSelect.options[0].value;
osSelect.addEventListener('change', function() {
osInputField.value = this.value;
console.log(this.value);
});
osInputField.parentNode.insertBefore(osSelect, osInputField.nextSibling);
osInputField.style.display = 'none';
if (sshKeys.length > 0) {
// Create dropdown options menu, then add it to the DOM then on change, update the regular input.
let sshSelect = document.createElement('select');
sshSelect.className = 'form-control';
sshKeys.forEach(function(sshkey) {
let option = document.createElement('option');
option.value = sshkey.id;
option.text = sshkey.name;
sshSelect.appendChild(option);
});
// Set the default value of the input field to the first option in the dropdown.
sshInputField.value = sshSelect.options[0].value;
sshSelect.addEventListener('change', function() {
sshInputField.value = this.value;
});
sshInputField.parentNode.insertBefore(sshSelect, sshInputField.nextSibling);
sshInputField.style.display = 'none';
} else {
sshInputField.style.display = 'none';
sshInputLabel.style.display = 'none';
}
});
</script>
";
});

View File

@@ -43,13 +43,33 @@ class ConfigureService extends Module
return null; return null;
} }
/**
* @param int $productId
* @return int|null
*/
public function fetchPackageByDbId(int $productId): ?int
{
$product = DB::table('tblproducts')->where('id', $productId)->first();
if (is_null($product)) {
return null;
}
return (int)$product->configoption2;
}
/** /**
* @param int $serverPackageId * @param int $serverPackageId
* @return array|null * @return array|null
* @throws JsonException * @throws JsonException
*/ */
public function fetchTemplates(int $serverPackageId): ?array public function fetchTemplates(?int $serverPackageId): ?array
{ {
if (is_null($serverPackageId)) {
return null;
}
$request = $this->initCurl($this->cp['token']); $request = $this->initCurl($this->cp['token']);
$response = $request->get( $response = $request->get(
@@ -96,4 +116,38 @@ class ConfigureService extends Module
return isset($response['msg']) && $response['msg'] === "ext_relation_id not found" ? null : $response['data']; return isset($response['msg']) && $response['msg'] === "ext_relation_id not found" ? null : $response['data'];
} }
/**
* @param int $id
* @param array $vars
* @return bool
*/
public function initServerBuild(int $id, array $vars): bool
{
$request = $this->initCurl($this->cp['token']);
// Generate a random 8 character hostname
$hostname = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 8);
$inputData = [
"operatingSystemId" => $vars['customfields']['Initial Operating System'],
"name" => $hostname,
"sshKeys" => [
$vars['customfields']['Initial SSH Key']
],
'email' => true
];
if (empty($vars['customfields']['Initial SSH Key'])) {
unset($inputData['sshKeys']);
}
$request->addOption(CURLOPT_POSTFIELDS, json_encode($inputData));
$request->post(
sprintf("%s/servers/%d/build", $this->cp['url'], $id)
);
return true;
}
} }

View File

@@ -163,6 +163,10 @@ class ModuleFunctions extends Module
Database::systemOnServerCreate($params['serviceid'], $data); Database::systemOnServerCreate($params['serviceid'], $data);
$this->updateWhmcsServiceParamsOnServerObject($params['serviceid'], $data); $this->updateWhmcsServiceParamsOnServerObject($params['serviceid'], $data);
// If the server is created successfully, we can initialize the server build.
$cs = new ConfigureService();
$cs->initServerBuild($data->data->id, $params);
/** /**
* *
* Server was created successfully. * Server was created successfully.
@@ -181,6 +185,46 @@ class ModuleFunctions extends Module
} }
} }
// This function was implemented by Zander Scott / awildboop of Blinkoh, LLC
// Please read this function thoroughly before use to ensure security & integrity
/**
* Allows changing of the package of a server
*
* @author https://github.com/BlinkohHost/virtfusion-whmcs-module
* @param $params
* @return string
*/
public function changePackage($params)
{
$service = Database::getSystemService($params['serviceid']);
if ($service) {
$whmcsService = Database::getWhmcsService($params['serviceid']);
$cp = $this->getCP($whmcsService->server);
$request = $this->initCurl($cp['token']);
$data = $request->put($cp['url'] . '/servers/' . $service->server_id . '/package/' . $params['configoption2']);
$data = json_decode($data);
Log::insert(__FUNCTION__, $request->getRequestInfo(), $data);
switch ($request->getRequestInfo('http_code')) {
case 204:
return 'success';
case 404:
return '404 was returned from the web service without the msg property. The service may be currently unavailable.';
case 423:
if (property_exists($data, 'msg')) {
return $data->msg;
}
default:
return 'Update package request failed. The web service reported HTTP code ' . $request->getRequestInfo('http_code');
}
}
return 'Service not found.';
}
/** /**
* *
* TERMINATE SERVER * TERMINATE SERVER