This commit is contained in:
shanec 2024-03-30 22:13:41 -04:00
commit 1bf67eef22
5 changed files with 491 additions and 0 deletions

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# service-virtfusionservice
A WemX service integration with VirtFusion

439
Service.php Normal file
View file

@ -0,0 +1,439 @@
<?php
namespace App\Services\VirtfusionService;
use Illuminate\Support\Facades\Http;
use App\Services\ServiceInterface;
use App\Models\Package;
use App\Models\Order;
use App\Models\ServiceAccount;
use Illuminate\Support\Str;
class Service implements ServiceInterface
{
/**
* Unique key used to store settings
* for this service.
*
* @return string
*/
public static $key = 'virtfusionserv';
public function __construct(Order $order)
{
$this->order = $order;
}
public function getDisplayName(): string
{
return settings('virtfusionserv::display_name', 'VirtFusion');
}
/**
* Returns the meta data about this Server/Service
*
* @return object
*/
public static function metaData(): object
{
return (object)
[
'display_name' => 'VirtfusionService',
'author' => 'Shane C.',
'version' => '1.0.0',
'wemx_version' => ['dev', '>=1.8.0'],
];
}
/**
* Define the default configuration values required to setup this service
* i.e host, api key, or other values. Use Laravel validation rules for
*
* Laravel validation rules: https://laravel.com/docs/10.x/validation
*
* @return array
*/
public static function setConfig(): array
{
$doesNotEndWithSlash = function ($attribute, $value, $fail) {
if (preg_match('/\/$/', $value)) {
return $fail('Panel URL must not end with a slash "/". It should be like https://panel.example.com');
}
};
return [
[
"key" => "virtfusionserv::host",
"name" => "VirtFusion Panel Host/URL",
"description" => "The Host/URL of the VirtFusion panel i.e https://panel.example.com",
"type" => "url",
"rules" => ["required", "active_url", $doesNotEndWithSlash]
],
[
"key" => "encrypted::virtfusionserv::apikey",
"name" => "VirtFusion API Key",
"description" => "API Key to manage VirtFusion panel.",
"type" => "password",
"rules" => ["required"],
]
];
}
/**
* Define the default package configuration values required when creatig
* new packages. i.e maximum ram usage, allowed databases and backups etc.
*
* Laravel validation rules: https://laravel.com/docs/10.x/validation
*
* @return array
*/
public static function setPackageConfig(Package $package): array
{
$response = Service::api('get', '/packages');
if ($response->failed()) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
$collectPackages = collect($response['data']);
$packages = $collectPackages->mapWithKeys(function ($item) {
if (!$item['enabled']) {
return [];
}
return [$item['id'] => $item['name']];
});
return [
[
"col" => "col-12",
"key" => "package",
"name" => "Package",
"description" => "Select the package to use for this service",
"type" => "select",
"options" => $packages->toArray(),
"save_on_change" => true,
"rules" => ['required'],
],
[
"col" => "col-12",
"key" => "hypervisor_group_id",
"name" => "Hypervisor Group ID",
"description" => "Enter the Hypervisor Group ID to use for this service",
"type" => "number",
"save_on_change" => true,
"rules" => ['required', 'numeric'],
],
[
"key" => "allowed_ips",
"name" => "Number of Allowed IPv4 IPs",
"description" => "Enter the number of allowed IPv4 IPs for this service",
"type" => "number",
"rules" => ['required', 'numeric'],
],
];
}
/**
* Define the checkout config that is required at checkout and is fillable by
* the client. Its important to properly sanatize all inputted data with rules
*
* Laravel validation rules: https://laravel.com/docs/10.x/validation
*
* @return array
*/
public static function setCheckoutConfig(Package $package): array
{
return [];
}
/**
* Define buttons shown at order management page
*
* @return array
*/
public static function setServiceButtons(Order $order): array
{
try {
$response = Service::api('post', "/users/{$order->user->id}/serverAuthenticationTokens/{$order->data['id']}");
if ($response->failed()) {
if (isset($response['errors'])) {
return [];
}
if (isset($response['message'])) {
return [];
}
}
} catch (Exception $e) {
return [];
}
$loginUrl = settings('virtfusionserv::host') . $response['data']['authentication']['endpoint_complete'];
return [
[
"name" => "Panel Login",
"color" => "primary",
"href" => $loginUrl,
"target" => "_blank",
]
];
}
/**
* This function is responsible for creating an instance of the
* service. This can be anything such as a server, vps or any other instance.
*
* @return void
*/
public function create(array $data = [])
{
$order = $this->order;
$user = $this->order->user;
$package = $this->order->package;
if (!$order->hasExternalUser()) {
$response = Service::api('post', '/users', [
"name" => $user->first_name . ' ' . $user->last_name,
"email" => $user->email,
"extRelationId" => $user->id,
"sendMail" => false,
]);
if ($response->failed() && $response->status() != 409) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
if ($response->status() == 409) {
$getUserRes = Service::api('get', "/users/{$user->id}/byExtRelation");
$virtFusionUser = $getUserRes['data'];
$order->createExternalUser([
'external_id' => $virtFusionUser['id'],
'username' => $user->email,
'password' => Str::random(16),
'data' => $virtFusionUser
]);
} else {
$order->createExternalUser([
'external_id' => $response['data']['id'], // optional
'username' => $user->email,
'password' => $response['data']['password'],
'data' => $response['data'], // Additional data about the user as an array (optional)
]);
}
$user->email([
'subject' => 'Panel Account Created',
'content' => "Your account has been created on the vps panel. You can login using the following details: <br><br> Email: {$user->email} <br> Password: {$response['data']['password']}",
'button' => [
'name' => 'VPS Panel',
'url' => settings('virtfusionserv::host'),
]
]);
}
// create the server
$createSrvRes = Service::api('post', '/servers', [
"packageId" => $package->data('package'),
"userId" => $order->getExternalUser()->external_id,
"hypervisorId" => $package->data('hypervisor_group_id'),
"ipv4" => $package->data('allowed_ips', 1),
]);
if ($createSrvRes->failed()) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
$order->external_id = $createSrvRes['data']['id'];
$order->data = $createSrvRes['data'];
$order->save();
return $createSrvRes;
}
/**
* This function is responsible for upgrading or downgrading
* an instance of this service. This method is optional
* If your service doesn't support upgrading, remove this method.
*
* Optional
* @return void
*/
public function upgrade(Package $oldPackage, Package $newPackage)
{
$order = $this->order;
$response = Service::api('put', "/servers/{$order->data['id']}/package/{$newPackage->data('package')}");
if ($response->failed()) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
}
/**
* This function is responsible for suspending an instance of the
* service. This method is called when a order is expired or
* suspended by an admin
*
* @return void
*/
public function suspend(array $data = [])
{
$order = $this->order;
$response = Service::api('post', "/servers/{$order->data['id']}/suspend");
if ($response->failed()) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
}
/**
* This function is responsible for unsuspending an instance of the
* service. This method is called when a order is activated or
* unsuspended by an admin
*
* @return void
*/
public function unsuspend(array $data = [])
{
$order = $this->order;
$response = Service::api('post', "/servers/{$order->data['id']}/unsuspend");
if ($response->failed()) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
}
/**
* This function is responsible for deleting an instance of the
* service. This can be anything such as a server, vps or any other instance.
*
* @return void
*/
public function terminate(array $data = [])
{
$order = $this->order;
Service::api('delete', "/servers/{$order->data['id']}?delay=5");
}
/**
* This function is responsible automatically logging in to the
* panel when the user clicks the login button in the client area.
*
* @return redirect
*/
public function loginToPanel(Order $order)
{
try {
$response = Service::api('post', "/users/{$order->user->id}/serverAuthenticationTokens/{$order->data['id']}");
if ($response->failed()) {
if (isset($response['errors'])) {
throw new Exception("[VirtFusion] " . json_encode($response['errors']));
}
if (isset($response['message'])) {
throw new Exception("[VirtFusion] " . $response['message']);
}
}
return redirect(settings('virtfusionserv::host') . $response['data']['authentication']['endpoint_complete']);
} catch (Exception $e) {
return redirect()->back()->withError("Something went wrong, please try again later.");
}
}
/**
* Test API connection
*/
public static function testConnection()
{
try {
// try to get list of packages through API request
$response = Service::api('get', '/connect');
} catch (Exception $error) {
// if try-catch fails, return the error with details
return redirect()->back()->withError("Failed to connect to VirtFusion. <br><br>{$error->getMessage()}");
}
// if no errors are logged, return a success message
return redirect()->back()->withSuccess("Successfully connected with VirtFusion");
}
public static function api($method, $endpoint, $data = [])
{
// validate the method
if (!in_array($method, ['get', 'post', 'put', 'delete', 'patch', 'head'])) {
throw new Exception("[VirtFusion] Invalid method: {$method}");
}
// make the request
$url = settings('virtfusionserv::host') . '/api/v1' . $endpoint;
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . settings('encrypted::virtfusionserv::apikey'),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
])->$method($url, $data);
// dd($response, $response->json());
if ($response->failed()) {
// dd($response, $response->json());
if ($response->unauthorized() or $response->forbidden()) {
throw new Exception("[VirtFusion] This action is unauthorized! Confirm that API token has the right permissions");
}
if ($response->serverError()) {
throw new Exception("[VirtFusion] Internal Server Error: {$response->status()}");
}
}
return $response;
}
}

23
composer.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "wemx/service-virtfusionservice",
"type": "laravel-module",
"description": "A service integration with VirtFusion",
"authors": [
{
"name": "Shane",
"email": "shane@scaffoe.com"
}
],
"extra": {
"module-dir": "app/Services",
"laravel": {
"providers": [],
"aliases": {}
}
},
"autoload": {
"psr-4": {
"App\\Services\\VirtfusionService\\": ""
}
}
}

11
module.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "VirtfusionService",
"alias": "virtfusionservice",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"App\\Services\\VirtfusionService\\Providers\\VirtfusionServiceServiceProvider"
],
"files": []
}

16
package.json Normal file
View file

@ -0,0 +1,16 @@
{
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^0.21.4",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
"laravel-vite-plugin": "^0.6.0",
"lodash": "^4.17.21",
"postcss": "^8.3.7",
"vite": "^3.0.9"
}
}