Enhance API with OIDC support, including login and callback endpoints. Update environment variables for OIDC configuration in .env.example. Add new features to the catalog service for listing software families, Minecraft versions, and deployment regions. Implement server management actions such as kill, delete, and update in the servers module. Integrate feature flags for maintenance mode in server operations. Update pnpm-lock.yaml with new dependencies and versions.
This commit is contained in:
@@ -1,7 +1,31 @@
|
||||
# Server provisioning module placeholder
|
||||
# HexaHost GameCloud WHMCS Server Module
|
||||
|
||||
Implementation planned in WHMCS Phase A–B.
|
||||
Provisioning module for WHMCS that integrates with the GameCloud Integration API.
|
||||
|
||||
Target install path: `/modules/servers/hexagamecloud/`
|
||||
## Features
|
||||
|
||||
See `integrations/whmcs/README.md` for the full module layout.
|
||||
- Provision, suspend, unsuspend, terminate, renew, change package
|
||||
- Client-area SSO to the GameCloud panel
|
||||
- Custom actions: **Start**, **Stop**, **Restart**, **Backup** (Integration API `actions/*`)
|
||||
- Admin service tab with live reconcile snapshot
|
||||
- DE/EN language files
|
||||
|
||||
## Configuration
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Hostname | GameCloud API base URL (e.g. `https://api.example.net`) |
|
||||
| Username | WHMCS integration ID |
|
||||
| Password | Integration API secret |
|
||||
|
||||
## Build package
|
||||
|
||||
```bash
|
||||
./build-package.sh
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
composer test
|
||||
```
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (!defined('WHMCS')) {
|
||||
die('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/lib/ApiClient.php';
|
||||
require_once __DIR__ . '/lib/Idempotency.php';
|
||||
require_once __DIR__ . '/lib/ServiceMetadata.php';
|
||||
require_once __DIR__ . '/../../../lib/ProductMapping.php';
|
||||
|
||||
const HEXAGAMECLOUD_SERVER_VERSION = '1.1.0';
|
||||
|
||||
function hexagamecloud_MetaData(): array
|
||||
{
|
||||
return [
|
||||
@@ -97,6 +103,36 @@ function hexagamecloud_TerminateAccount(array $params): string
|
||||
return hexagamecloud_lifecycleAction($params, 'terminate');
|
||||
}
|
||||
|
||||
function hexagamecloud_Renew(array $params): string
|
||||
{
|
||||
try {
|
||||
$client = new HexaGameCloudApiClient($params);
|
||||
$serviceId = (string) $params['serviceid'];
|
||||
$payload = array_filter([
|
||||
'invoiceId' => hexagamecloud_resolveRenewInvoiceId($params),
|
||||
'periodEnd' => hexagamecloud_formatIso8601($params['nextduedate'] ?? null),
|
||||
], static fn ($value) => $value !== null && $value !== '');
|
||||
|
||||
$client->renewService(
|
||||
$serviceId,
|
||||
$payload,
|
||||
HexaGameCloudIdempotency::renewKey($serviceId, $params)
|
||||
);
|
||||
|
||||
return 'success';
|
||||
} catch (Throwable $exception) {
|
||||
logModuleCall(
|
||||
'hexagamecloud',
|
||||
__FUNCTION__,
|
||||
$params,
|
||||
$exception->getMessage(),
|
||||
$exception->getMessage(),
|
||||
['password', 'secret', 'apiSecret']
|
||||
);
|
||||
return $exception->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function hexagamecloud_ChangePackage(array $params): string
|
||||
{
|
||||
try {
|
||||
@@ -121,6 +157,137 @@ function hexagamecloud_AdminSingleSignOn(array $params)
|
||||
return hexagamecloud_requestSso($params, 'admin');
|
||||
}
|
||||
|
||||
function hexagamecloud_ClientArea(array $params): array
|
||||
{
|
||||
hexagamecloud_loadLanguage($params);
|
||||
|
||||
$serviceId = (string) $params['serviceid'];
|
||||
$snapshot = hexagamecloud_fetchServiceSnapshot($params);
|
||||
$server = $snapshot['service']['server'] ?? [];
|
||||
$linkStatus = (string) ($snapshot['service']['status'] ?? 'PENDING');
|
||||
|
||||
return [
|
||||
'templatefile' => 'templates/clientarea',
|
||||
'vars' => [
|
||||
'LANG' => hexagamecloud_langVars(),
|
||||
'apiWarning' => $snapshot['warning'],
|
||||
'notProvisioned' => $linkStatus === 'PENDING' && ($server['id'] ?? '') === '',
|
||||
'serverName' => (string) ($server['name'] ?? ($params['domain'] ?? '')),
|
||||
'technicalStatus' => (string) ($server['status'] ?? 'UNKNOWN'),
|
||||
'joinAddress' => (string) ($server['joinHostname'] ?? '-'),
|
||||
'edition' => (string) ($server['edition'] ?? 'JAVA'),
|
||||
'softwareFamily' => (string) ($server['softwareFamily'] ?? HexaGameCloudProductMapping::resolveSoftwareFamily($params)),
|
||||
'minecraftVersion' => (string) ($server['minecraftVersion'] ?? HexaGameCloudProductMapping::resolveMinecraftVersion($params)),
|
||||
'planSlug' => HexaGameCloudProductMapping::resolvePlanSlug($params),
|
||||
'ramMb' => (int) ($server['ramMb'] ?? 0),
|
||||
'nextDueDate' => (string) ($params['nextduedate'] ?? '-'),
|
||||
'billingStatus' => $linkStatus === 'SUSPENDED'
|
||||
? hexagamecloud_lang('hexagamecloud_suspended_yes')
|
||||
: hexagamecloud_lang('hexagamecloud_suspended_no'),
|
||||
'lastSync' => $snapshot['syncedAt'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function hexagamecloud_ClientAreaCustomButtonArray(): array
|
||||
{
|
||||
return [
|
||||
hexagamecloud_lang('hexagamecloud_manage_panel') => 'ManagePanel',
|
||||
hexagamecloud_lang('hexagamecloud_action_start') => 'StartServer',
|
||||
hexagamecloud_lang('hexagamecloud_action_stop') => 'StopServer',
|
||||
hexagamecloud_lang('hexagamecloud_action_restart') => 'RestartServer',
|
||||
hexagamecloud_lang('hexagamecloud_action_backup') => 'BackupServer',
|
||||
];
|
||||
}
|
||||
|
||||
function hexagamecloud_StartServer(array $params): string
|
||||
{
|
||||
return hexagamecloud_serviceAction($params, 'start');
|
||||
}
|
||||
|
||||
function hexagamecloud_StopServer(array $params): string
|
||||
{
|
||||
return hexagamecloud_serviceAction($params, 'stop');
|
||||
}
|
||||
|
||||
function hexagamecloud_RestartServer(array $params): string
|
||||
{
|
||||
return hexagamecloud_serviceAction($params, 'restart');
|
||||
}
|
||||
|
||||
function hexagamecloud_BackupServer(array $params): string
|
||||
{
|
||||
return hexagamecloud_serviceAction($params, 'backup');
|
||||
}
|
||||
|
||||
function hexagamecloud_serviceAction(array $params, string $action): string
|
||||
{
|
||||
try {
|
||||
$client = new HexaGameCloudApiClient($params);
|
||||
$serviceId = (string) $params['serviceid'];
|
||||
if ($action === 'start') {
|
||||
$client->startService($serviceId);
|
||||
} elseif ($action === 'stop') {
|
||||
$client->stopService($serviceId);
|
||||
} elseif ($action === 'restart') {
|
||||
$client->restartService($serviceId);
|
||||
} else {
|
||||
$client->backupService($serviceId);
|
||||
}
|
||||
return 'success';
|
||||
} catch (Throwable $exception) {
|
||||
logModuleCall('hexagamecloud', $action, $params, $exception->getMessage());
|
||||
return $exception->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function hexagamecloud_ManagePanel(array $params): array
|
||||
{
|
||||
return hexagamecloud_requestSso($params, 'service');
|
||||
}
|
||||
|
||||
function hexagamecloud_AdminServicesTabFields(array $params): array
|
||||
{
|
||||
hexagamecloud_loadLanguage($params);
|
||||
|
||||
$serviceId = (string) $params['serviceid'];
|
||||
$snapshot = hexagamecloud_fetchServiceSnapshot($params);
|
||||
$service = $snapshot['service'];
|
||||
$server = $service['server'] ?? [];
|
||||
$metadata = HexaGameCloudServiceMetadata::load($serviceId);
|
||||
$warning = $snapshot['warning'] !== null
|
||||
? '<div class="alert alert-warning">' . htmlspecialchars($snapshot['warning'], ENT_QUOTES, 'UTF-8') . '</div>'
|
||||
: '';
|
||||
|
||||
return [
|
||||
hexagamecloud_lang('hexagamecloud_admin_server_id') => $warning . htmlspecialchars((string) ($server['id'] ?? '-'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_user_id') => htmlspecialchars((string) ($server['userId'] ?? '-'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_link_status') => htmlspecialchars((string) ($service['status'] ?? 'PENDING'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_technical_status') => htmlspecialchars((string) ($server['status'] ?? 'UNKNOWN'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_node') => htmlspecialchars((string) ($server['nodeId'] ?? '-'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_host_port') => htmlspecialchars((string) ($server['hostPort'] ?? '-'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_plan') => htmlspecialchars(HexaGameCloudProductMapping::resolvePlanSlug($params), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_ram') => htmlspecialchars((string) ((int) ($server['ramMb'] ?? 0)) . ' MiB', ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_last_sync') => htmlspecialchars((string) ($snapshot['syncedAt'] ?? '-'), ENT_QUOTES, 'UTF-8'),
|
||||
hexagamecloud_lang('hexagamecloud_admin_correlation_note') => '<textarea class="form-control" rows="3" name="hexagamecloud_correlation_note">'
|
||||
. htmlspecialchars((string) ($metadata['correlation_note'] ?? ''), ENT_QUOTES, 'UTF-8')
|
||||
. '</textarea>',
|
||||
];
|
||||
}
|
||||
|
||||
function hexagamecloud_AdminServicesTabFieldsSave(array $params): void
|
||||
{
|
||||
$serviceId = (string) $params['serviceid'];
|
||||
$note = trim((string) ($params['hexagamecloud_correlation_note'] ?? ''));
|
||||
$existing = HexaGameCloudServiceMetadata::load($serviceId);
|
||||
|
||||
HexaGameCloudServiceMetadata::save($serviceId, array_merge($existing, [
|
||||
'correlation_note' => $note,
|
||||
'updated_at' => gmdate('c'),
|
||||
'updated_by_admin_id' => (string) ($params['adminid'] ?? ''),
|
||||
]));
|
||||
}
|
||||
|
||||
function hexagamecloud_TestConnection(array $params): array
|
||||
{
|
||||
try {
|
||||
@@ -233,6 +400,103 @@ function hexagamecloud_lifecycleAction(array $params, string $action, array $bod
|
||||
}
|
||||
}
|
||||
|
||||
function hexagamecloud_fetchServiceSnapshot(array $params): array
|
||||
{
|
||||
$serviceId = (string) $params['serviceid'];
|
||||
$cached = HexaGameCloudServiceMetadata::load($serviceId);
|
||||
$snapshot = [
|
||||
'service' => $cached['service'] ?? [
|
||||
'externalServiceId' => $serviceId,
|
||||
'status' => 'PENDING',
|
||||
'server' => [],
|
||||
],
|
||||
'warning' => null,
|
||||
'syncedAt' => $cached['synced_at'] ?? null,
|
||||
];
|
||||
|
||||
try {
|
||||
$client = new HexaGameCloudApiClient($params);
|
||||
$service = $client->getService($serviceId);
|
||||
$snapshot['service'] = $service;
|
||||
$snapshot['syncedAt'] = gmdate('c');
|
||||
|
||||
HexaGameCloudServiceMetadata::save($serviceId, [
|
||||
'service' => $service,
|
||||
'synced_at' => $snapshot['syncedAt'],
|
||||
'correlation_note' => $cached['correlation_note'] ?? '',
|
||||
'updated_by_admin_id' => $cached['updated_by_admin_id'] ?? '',
|
||||
]);
|
||||
} catch (Throwable $exception) {
|
||||
logModuleCall('hexagamecloud', 'fetchServiceSnapshot', $params, $exception->getMessage());
|
||||
$snapshot['warning'] = hexagamecloud_lang('hexagamecloud_api_unavailable');
|
||||
}
|
||||
|
||||
return $snapshot;
|
||||
}
|
||||
|
||||
function hexagamecloud_resolveRenewInvoiceId(array $params): ?string
|
||||
{
|
||||
$invoiceId = (string) ($params['invoiceid'] ?? '');
|
||||
if ($invoiceId !== '') {
|
||||
return $invoiceId;
|
||||
}
|
||||
|
||||
if (!empty($params['model']['invoiceid'])) {
|
||||
return (string) $params['model']['invoiceid'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function hexagamecloud_formatIso8601(?string $value): ?string
|
||||
{
|
||||
if ($value === null || trim($value) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$timestamp = strtotime($value);
|
||||
if ($timestamp === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return gmdate('c', $timestamp);
|
||||
}
|
||||
|
||||
function hexagamecloud_loadLanguage(array $params): void
|
||||
{
|
||||
static $loaded = false;
|
||||
if ($loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$language = strtolower((string) ($params['clientsdetails']['language'] ?? $params['language'] ?? 'english'));
|
||||
if (!in_array($language, ['english', 'german'], true)) {
|
||||
$language = 'english';
|
||||
}
|
||||
|
||||
$langFile = __DIR__ . '/lang/' . $language . '.php';
|
||||
if (!is_readable($langFile)) {
|
||||
$langFile = __DIR__ . '/lang/english.php';
|
||||
}
|
||||
|
||||
require $langFile;
|
||||
$loaded = true;
|
||||
}
|
||||
|
||||
function hexagamecloud_lang(string $key): string
|
||||
{
|
||||
global $_LANG;
|
||||
|
||||
return (string) ($_LANG[$key] ?? $key);
|
||||
}
|
||||
|
||||
function hexagamecloud_langVars(): array
|
||||
{
|
||||
global $_LANG;
|
||||
|
||||
return is_array($_LANG ?? null) ? $_LANG : [];
|
||||
}
|
||||
|
||||
function hexagamecloud_requestSso(array $params, string $kind)
|
||||
{
|
||||
try {
|
||||
|
||||
68
integrations/whmcs/modules/servers/hexagamecloud/hooks.php
Normal file
68
integrations/whmcs/modules/servers/hexagamecloud/hooks.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (!defined('WHMCS')) {
|
||||
die('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
if (!defined('HEXAGAMECLOUD_SERVER_VERSION')) {
|
||||
define('HEXAGAMECLOUD_SERVER_VERSION', '1.1.0');
|
||||
}
|
||||
|
||||
add_hook('AfterModuleRenew', 1, function (array $vars): void {
|
||||
if (($vars['params']['moduletype'] ?? '') !== 'hexagamecloud') {
|
||||
return;
|
||||
}
|
||||
|
||||
$serviceId = (string) ($vars['params']['serviceid'] ?? '');
|
||||
if ($serviceId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
logActivity('HexaGameCloud service renewed via WHMCS (service #' . $serviceId . ')');
|
||||
});
|
||||
|
||||
add_hook('AfterModuleCreate', 1, function (array $vars): void {
|
||||
if (($vars['params']['moduletype'] ?? '') !== 'hexagamecloud') {
|
||||
return;
|
||||
}
|
||||
|
||||
$serviceId = (string) ($vars['params']['serviceid'] ?? '');
|
||||
if ($serviceId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
logActivity('HexaGameCloud service provisioned (service #' . $serviceId . ')');
|
||||
});
|
||||
|
||||
add_hook('ClientAreaPageProductDetails', 1, function (array $vars): array {
|
||||
if (($vars['module'] ?? '') !== 'hexagamecloud') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'gamecloudModuleVersion' => HEXAGAMECLOUD_SERVER_VERSION,
|
||||
];
|
||||
});
|
||||
|
||||
add_hook('AdminAreaHeadOutput', 1, function (array $vars): string {
|
||||
if (($vars['filename'] ?? '') !== 'clientsservices') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '<!-- HexaGameCloud server module ' . htmlspecialchars(HEXAGAMECLOUD_SERVER_VERSION, ENT_QUOTES, 'UTF-8') . ' -->';
|
||||
});
|
||||
|
||||
add_hook('ServiceEdit', 1, function (array $vars): void {
|
||||
if (($vars['moduletype'] ?? '') !== 'hexagamecloud') {
|
||||
return;
|
||||
}
|
||||
|
||||
$serviceId = (string) ($vars['serviceid'] ?? '');
|
||||
if ($serviceId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
logActivity('HexaGameCloud service edited in WHMCS admin (service #' . $serviceId . ')');
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
if (!defined('WHMCS')) {
|
||||
die('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
$_LANG['hexagamecloud_manage_panel'] = 'Manage in GameCloud Panel';
|
||||
$_LANG['hexagamecloud_action_start'] = 'Start Server';
|
||||
$_LANG['hexagamecloud_action_stop'] = 'Stop Server';
|
||||
$_LANG['hexagamecloud_action_restart'] = 'Restart Server';
|
||||
$_LANG['hexagamecloud_action_backup'] = 'Create Backup';
|
||||
$_LANG['hexagamecloud_server_overview'] = 'Game Server Overview';
|
||||
$_LANG['hexagamecloud_server_name'] = 'Server Name';
|
||||
$_LANG['hexagamecloud_status'] = 'Status';
|
||||
$_LANG['hexagamecloud_join_address'] = 'Join Address';
|
||||
$_LANG['hexagamecloud_edition'] = 'Edition';
|
||||
$_LANG['hexagamecloud_software'] = 'Software';
|
||||
$_LANG['hexagamecloud_version'] = 'Version';
|
||||
$_LANG['hexagamecloud_plan'] = 'Plan';
|
||||
$_LANG['hexagamecloud_ram'] = 'RAM';
|
||||
$_LANG['hexagamecloud_next_due'] = 'Next Billing Date';
|
||||
$_LANG['hexagamecloud_suspend_status'] = 'Billing Suspension';
|
||||
$_LANG['hexagamecloud_api_unavailable'] = 'GameCloud is temporarily unavailable. Cached data may be shown.';
|
||||
$_LANG['hexagamecloud_last_sync'] = 'Last successful sync';
|
||||
$_LANG['hexagamecloud_not_provisioned'] = 'This service has not been provisioned in GameCloud yet.';
|
||||
$_LANG['hexagamecloud_admin_server_id'] = 'GameCloud Server ID';
|
||||
$_LANG['hexagamecloud_admin_user_id'] = 'GameCloud User ID';
|
||||
$_LANG['hexagamecloud_admin_link_status'] = 'Link Status';
|
||||
$_LANG['hexagamecloud_admin_technical_status'] = 'Technical Status';
|
||||
$_LANG['hexagamecloud_admin_node'] = 'Node';
|
||||
$_LANG['hexagamecloud_admin_host_port'] = 'Host Port';
|
||||
$_LANG['hexagamecloud_admin_correlation_note'] = 'Admin Correlation Note';
|
||||
$_LANG['hexagamecloud_admin_last_sync'] = 'Last Sync';
|
||||
$_LANG['hexagamecloud_suspended_yes'] = 'Suspended';
|
||||
$_LANG['hexagamecloud_suspended_no'] = 'Active';
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
if (!defined('WHMCS')) {
|
||||
die('This file cannot be accessed directly');
|
||||
}
|
||||
|
||||
$_LANG['hexagamecloud_manage_panel'] = 'Im GameCloud-Panel verwalten';
|
||||
$_LANG['hexagamecloud_action_start'] = 'Server starten';
|
||||
$_LANG['hexagamecloud_action_stop'] = 'Server stoppen';
|
||||
$_LANG['hexagamecloud_action_restart'] = 'Server neu starten';
|
||||
$_LANG['hexagamecloud_action_backup'] = 'Backup erstellen';
|
||||
$_LANG['hexagamecloud_server_overview'] = 'Spieleserver-Übersicht';
|
||||
$_LANG['hexagamecloud_server_name'] = 'Servername';
|
||||
$_LANG['hexagamecloud_status'] = 'Status';
|
||||
$_LANG['hexagamecloud_join_address'] = 'Join-Adresse';
|
||||
$_LANG['hexagamecloud_edition'] = 'Edition';
|
||||
$_LANG['hexagamecloud_software'] = 'Software';
|
||||
$_LANG['hexagamecloud_version'] = 'Version';
|
||||
$_LANG['hexagamecloud_plan'] = 'Tarif';
|
||||
$_LANG['hexagamecloud_ram'] = 'Arbeitsspeicher';
|
||||
$_LANG['hexagamecloud_next_due'] = 'Nächstes Abrechnungsdatum';
|
||||
$_LANG['hexagamecloud_suspend_status'] = 'Abrechnungssperre';
|
||||
$_LANG['hexagamecloud_api_unavailable'] = 'GameCloud ist vorübergehend nicht erreichbar. Es können zwischengespeicherte Daten angezeigt werden.';
|
||||
$_LANG['hexagamecloud_last_sync'] = 'Letzte erfolgreiche Synchronisierung';
|
||||
$_LANG['hexagamecloud_not_provisioned'] = 'Dieser Dienst wurde in GameCloud noch nicht bereitgestellt.';
|
||||
$_LANG['hexagamecloud_admin_server_id'] = 'GameCloud-Server-ID';
|
||||
$_LANG['hexagamecloud_admin_user_id'] = 'GameCloud-Benutzer-ID';
|
||||
$_LANG['hexagamecloud_admin_link_status'] = 'Verknüpfungsstatus';
|
||||
$_LANG['hexagamecloud_admin_technical_status'] = 'Technischer Status';
|
||||
$_LANG['hexagamecloud_admin_node'] = 'Node';
|
||||
$_LANG['hexagamecloud_admin_host_port'] = 'Host-Port';
|
||||
$_LANG['hexagamecloud_admin_correlation_note'] = 'Admin-Korrelationsnotiz';
|
||||
$_LANG['hexagamecloud_admin_last_sync'] = 'Letzte Synchronisierung';
|
||||
$_LANG['hexagamecloud_suspended_yes'] = 'Gesperrt';
|
||||
$_LANG['hexagamecloud_suspended_no'] = 'Aktiv';
|
||||
@@ -82,6 +82,24 @@ final class HexaGameCloudApiClient
|
||||
);
|
||||
}
|
||||
|
||||
public function getService(string $externalServiceId): array
|
||||
{
|
||||
return $this->request(
|
||||
'GET',
|
||||
'/api/v1/integrations/whmcs/services/' . rawurlencode($externalServiceId)
|
||||
);
|
||||
}
|
||||
|
||||
public function renewService(string $externalServiceId, array $payload, string $idempotencyKey): array
|
||||
{
|
||||
return $this->request(
|
||||
'POST',
|
||||
'/api/v1/integrations/whmcs/services/' . rawurlencode($externalServiceId) . '/renew',
|
||||
$payload,
|
||||
$idempotencyKey
|
||||
);
|
||||
}
|
||||
|
||||
public function changePackage(string $externalServiceId, array $payload): array
|
||||
{
|
||||
return $this->request(
|
||||
@@ -173,6 +191,46 @@ final class HexaGameCloudApiClient
|
||||
);
|
||||
}
|
||||
|
||||
public function startService(string $externalServiceId): array
|
||||
{
|
||||
return $this->request(
|
||||
'POST',
|
||||
'/api/v1/integrations/whmcs/services/' . rawurlencode($externalServiceId) . '/actions/start',
|
||||
[],
|
||||
'service:start:' . $externalServiceId
|
||||
);
|
||||
}
|
||||
|
||||
public function stopService(string $externalServiceId): array
|
||||
{
|
||||
return $this->request(
|
||||
'POST',
|
||||
'/api/v1/integrations/whmcs/services/' . rawurlencode($externalServiceId) . '/actions/stop',
|
||||
[],
|
||||
'service:stop:' . $externalServiceId
|
||||
);
|
||||
}
|
||||
|
||||
public function restartService(string $externalServiceId): array
|
||||
{
|
||||
return $this->request(
|
||||
'POST',
|
||||
'/api/v1/integrations/whmcs/services/' . rawurlencode($externalServiceId) . '/actions/restart',
|
||||
[],
|
||||
'service:restart:' . $externalServiceId
|
||||
);
|
||||
}
|
||||
|
||||
public function backupService(string $externalServiceId): array
|
||||
{
|
||||
return $this->request(
|
||||
'POST',
|
||||
'/api/v1/integrations/whmcs/services/' . rawurlencode($externalServiceId) . '/actions/backup',
|
||||
[],
|
||||
'service:backup:' . $externalServiceId
|
||||
);
|
||||
}
|
||||
|
||||
private function request(string $method, string $pathWithQuery, array $body = [], ?string $idempotencySuffix = null): array
|
||||
{
|
||||
$pathParts = explode('?', $pathWithQuery, 2);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class HexaGameCloudIdempotency
|
||||
{
|
||||
public static function serviceOperationKey(string $operation, string $externalServiceId, ?string $reference = null): string
|
||||
{
|
||||
$suffix = ($reference !== null && $reference !== '') ? $reference : gmdate('Y-m-d');
|
||||
|
||||
return 'service:' . $operation . ':' . $externalServiceId . ':' . $suffix;
|
||||
}
|
||||
|
||||
public static function renewKey(string $externalServiceId, array $params): string
|
||||
{
|
||||
$reference = (string) ($params['invoiceid'] ?? '');
|
||||
if ($reference === '' && !empty($params['model']['invoiceid'])) {
|
||||
$reference = (string) $params['model']['invoiceid'];
|
||||
}
|
||||
if ($reference === '' && !empty($params['renewalid'])) {
|
||||
$reference = (string) $params['renewalid'];
|
||||
}
|
||||
|
||||
return self::serviceOperationKey('renew', $externalServiceId, $reference !== '' ? $reference : null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use WHMCS\Database\Capsule;
|
||||
|
||||
final class HexaGameCloudServiceMetadata
|
||||
{
|
||||
private const OPERATION_PREFIX = 'service:admin-meta:';
|
||||
|
||||
public static function load(string $serviceId): array
|
||||
{
|
||||
try {
|
||||
$row = Capsule::table('mod_hexagamecloud_operations')
|
||||
->where('operation', self::OPERATION_PREFIX . $serviceId)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
if ($row === null || empty($row->metadata)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode((string) $row->metadata, true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public static function save(string $serviceId, array $data): void
|
||||
{
|
||||
$payload = json_encode($data, JSON_THROW_ON_ERROR);
|
||||
|
||||
try {
|
||||
$existing = Capsule::table('mod_hexagamecloud_operations')
|
||||
->where('operation', self::OPERATION_PREFIX . $serviceId)
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
if ($existing !== null) {
|
||||
Capsule::table('mod_hexagamecloud_operations')
|
||||
->where('id', $existing->id)
|
||||
->update([
|
||||
'metadata' => $payload,
|
||||
'status' => 'saved',
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
Capsule::table('mod_hexagamecloud_operations')->insert([
|
||||
'operation' => self::OPERATION_PREFIX . $serviceId,
|
||||
'status' => 'saved',
|
||||
'metadata' => $payload,
|
||||
'created_at' => gmdate('Y-m-d H:i:s'),
|
||||
]);
|
||||
} catch (Throwable $exception) {
|
||||
logActivity('HexaGameCloud admin metadata save failed: ' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{$LANG.hexagamecloud_server_overview}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{if $apiWarning}
|
||||
<div class="alert alert-warning">{$apiWarning}</div>
|
||||
{/if}
|
||||
{if $notProvisioned}
|
||||
<p class="text-muted">{$LANG.hexagamecloud_not_provisioned}</p>
|
||||
{else}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{$LANG.hexagamecloud_server_name}</dt>
|
||||
<dd>{$serverName}</dd>
|
||||
<dt>{$LANG.hexagamecloud_status}</dt>
|
||||
<dd>{$technicalStatus}</dd>
|
||||
<dt>{$LANG.hexagamecloud_join_address}</dt>
|
||||
<dd>{$joinAddress}</dd>
|
||||
<dt>{$LANG.hexagamecloud_edition}</dt>
|
||||
<dd>{$edition}</dd>
|
||||
<dt>{$LANG.hexagamecloud_software}</dt>
|
||||
<dd>{$softwareFamily}</dd>
|
||||
<dt>{$LANG.hexagamecloud_version}</dt>
|
||||
<dd>{$minecraftVersion}</dd>
|
||||
<dt>{$LANG.hexagamecloud_plan}</dt>
|
||||
<dd>{$planSlug}</dd>
|
||||
<dt>{$LANG.hexagamecloud_ram}</dt>
|
||||
<dd>{$ramMb} MiB</dd>
|
||||
<dt>{$LANG.hexagamecloud_next_due}</dt>
|
||||
<dd>{$nextDueDate}</dd>
|
||||
<dt>{$LANG.hexagamecloud_suspend_status}</dt>
|
||||
<dd>{$billingStatus}</dd>
|
||||
</dl>
|
||||
{if $lastSync}
|
||||
<p class="text-muted"><small>{$LANG.hexagamecloud_last_sync}: {$lastSync}</small></p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user