mirror of
https://github.com/xkatld/lxdapi-web-server.git
synced 2026-06-02 09:20:59 +08:00
feat: 添加whmcs插件
This commit is contained in:
34
whmcs/lxdapiserver/functions.php
Normal file
34
whmcs/lxdapiserver/functions.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use WHMCS\Database\Capsule;
|
||||
|
||||
function lxdapiserver_generate_password($length = 16)
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*';
|
||||
$password = '';
|
||||
$charactersLength = strlen($characters);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$password .= $characters[rand(0, $charactersLength - 1)];
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
function lxdapiserver_save_password($serviceid, $password)
|
||||
{
|
||||
try {
|
||||
Capsule::table('tblhosting')
|
||||
->where('id', $serviceid)
|
||||
->update(['password' => encrypt($password)]);
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
lxdapiserver_log_error('save_password', $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_log_error($function, $message)
|
||||
{
|
||||
logActivity('[lxdapi Server] ' . $function . ': ' . $message);
|
||||
}
|
||||
94
whmcs/lxdapiserver/lib/lxd_api.php
Normal file
94
whmcs/lxdapiserver/lib/lxd_api.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
class LXD_API
|
||||
{
|
||||
private $serverip;
|
||||
private $serverport;
|
||||
private $apikey;
|
||||
private $protocol = 'https';
|
||||
private $timeout = 30;
|
||||
|
||||
public function __construct($params)
|
||||
{
|
||||
$this->serverip = $params['serverip'];
|
||||
$this->serverport = $params['serverport'] ?: '8848';
|
||||
$this->apikey = $params['serveraccesshash'];
|
||||
}
|
||||
|
||||
public function get($endpoint, $data = [])
|
||||
{
|
||||
return $this->request('GET', $endpoint, $data);
|
||||
}
|
||||
|
||||
public function post($endpoint, $data = [])
|
||||
{
|
||||
return $this->request('POST', $endpoint, $data);
|
||||
}
|
||||
|
||||
public function delete($endpoint, $data = [])
|
||||
{
|
||||
return $this->request('DELETE', $endpoint, $data);
|
||||
}
|
||||
|
||||
private function request($method, $endpoint, $data = [])
|
||||
{
|
||||
$url = $this->protocol . '://' . $this->serverip . ':' . $this->serverport . $endpoint;
|
||||
|
||||
if ($method === 'GET' && !empty($data) && strpos($endpoint, '?') === false) {
|
||||
$url .= '?' . http_build_query($data);
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
$headers = [
|
||||
'X-API-Hash: ' . $this->apikey,
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
|
||||
]);
|
||||
|
||||
if (($method === 'POST' || $method === 'DELETE') && !empty($data)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlErrno) {
|
||||
throw new Exception("连接失败: " . $curlError);
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("响应解析失败: " . json_last_error_msg());
|
||||
}
|
||||
|
||||
if ($httpCode >= 200 && $httpCode < 300) {
|
||||
return [
|
||||
'success' => isset($decoded['code']) ? $decoded['code'] == 200 : true,
|
||||
'message' => $decoded['msg'] ?? $decoded['message'] ?? '',
|
||||
'data' => $decoded['data'] ?? $decoded,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $decoded['msg'] ?? $decoded['message'] ?? 'HTTP ' . $httpCode,
|
||||
'data' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
560
whmcs/lxdapiserver/lxdapiserver.php
Normal file
560
whmcs/lxdapiserver/lxdapiserver.php
Normal file
@@ -0,0 +1,560 @@
|
||||
<?php
|
||||
/**
|
||||
* WHMCS-LXD对接插件 by xkatld
|
||||
*
|
||||
* @package WHMCS-LXD对接插件 by xkatld
|
||||
* @author xkatld
|
||||
* @version v2.0.0-main
|
||||
* @link https://github.com/xkatld/lxdapi-web-server
|
||||
*/
|
||||
|
||||
if (!defined("WHMCS")) {
|
||||
die("This file cannot be accessed directly");
|
||||
}
|
||||
|
||||
use WHMCS\Database\Capsule;
|
||||
|
||||
require_once __DIR__ . '/lib/lxd_api.php';
|
||||
require_once __DIR__ . '/functions.php';
|
||||
|
||||
function lxdapiserver_MetaData()
|
||||
{
|
||||
return [
|
||||
'DisplayName' => 'WHMCS-LXD对接插件 by xkatld',
|
||||
'APIVersion' => 'v2.0.0-main',
|
||||
'RequiresServer' => true,
|
||||
'DefaultNonSSLPort' => '8848',
|
||||
'DefaultSSLPort' => '8848',
|
||||
'ServiceSingleSignOnLabel' => 'Login to Console',
|
||||
'AdminSingleSignOnLabel' => 'Login to Console as Admin',
|
||||
];
|
||||
}
|
||||
|
||||
function lxdapiserver_TestConnection(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->get('/api/system/containers', []);
|
||||
|
||||
if ($result['success']) {
|
||||
return [
|
||||
'success' => true,
|
||||
'error' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $result['message'] ?: '连接失败',
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_ConfigOptions()
|
||||
{
|
||||
return [
|
||||
'cpus' => [
|
||||
'FriendlyName' => 'CPU核心数',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '1',
|
||||
'Description' => 'CPU核心数量',
|
||||
],
|
||||
'memory' => [
|
||||
'FriendlyName' => '内存 (MB)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '512',
|
||||
'Description' => '内存大小,单位:MB',
|
||||
],
|
||||
'disk' => [
|
||||
'FriendlyName' => '硬盘 (MB)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '1024',
|
||||
'Description' => '硬盘大小,单位:MB',
|
||||
],
|
||||
'image' => [
|
||||
'FriendlyName' => '系统镜像',
|
||||
'Type' => 'text',
|
||||
'Size' => '25',
|
||||
'Default' => 'alpine320',
|
||||
'Description' => '系统镜像名称',
|
||||
],
|
||||
'ingress' => [
|
||||
'FriendlyName' => '入站带宽 (Mbit)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '100',
|
||||
'Description' => '下载速度限制,单位:Mbit',
|
||||
],
|
||||
'egress' => [
|
||||
'FriendlyName' => '出站带宽 (Mbit)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '100',
|
||||
'Description' => '上传速度限制,单位:Mbit',
|
||||
],
|
||||
'traffic_limit' => [
|
||||
'FriendlyName' => '月流量限制 (GB)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '100',
|
||||
'Description' => '单位:GB',
|
||||
],
|
||||
'ipv4_pool_limit' => [
|
||||
'FriendlyName' => 'IPv4地址池限制',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '0',
|
||||
'Description' => 'IPv4独立地址数量上限',
|
||||
],
|
||||
'ipv4_mapping_limit' => [
|
||||
'FriendlyName' => 'IPv4端口映射限制',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '0',
|
||||
'Description' => 'IPv4端口转发规则上限',
|
||||
],
|
||||
'ipv6_pool_limit' => [
|
||||
'FriendlyName' => 'IPv6地址池限制',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '0',
|
||||
'Description' => 'IPv6独立地址数量上限',
|
||||
],
|
||||
'ipv6_mapping_limit' => [
|
||||
'FriendlyName' => 'IPv6端口映射限制',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '0',
|
||||
'Description' => 'IPv6端口转发规则上限',
|
||||
],
|
||||
'reverse_proxy_limit' => [
|
||||
'FriendlyName' => '反向代理限制',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '0',
|
||||
'Description' => '反向代理域名数量上限',
|
||||
],
|
||||
'cpu_allowance' => [
|
||||
'FriendlyName' => 'CPU使用率限制 (%)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '50',
|
||||
'Description' => 'CPU占用百分比,单位:%',
|
||||
],
|
||||
'io_read' => [
|
||||
'FriendlyName' => '磁盘读取限制 (MB/s)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '100',
|
||||
'Description' => '单位:MB/s',
|
||||
],
|
||||
'io_write' => [
|
||||
'FriendlyName' => '磁盘写入限制 (MB/s)',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '50',
|
||||
'Description' => '单位:MB/s',
|
||||
],
|
||||
'processes_limit' => [
|
||||
'FriendlyName' => '最大进程数',
|
||||
'Type' => 'text',
|
||||
'Size' => '10',
|
||||
'Default' => '512',
|
||||
'Description' => '进程数量上限',
|
||||
],
|
||||
'allow_nesting' => [
|
||||
'FriendlyName' => '嵌套虚拟化',
|
||||
'Type' => 'dropdown',
|
||||
'Options' => 'true,false',
|
||||
'Default' => 'true',
|
||||
'Description' => '支持Docker等虚拟化',
|
||||
],
|
||||
'memory_swap' => [
|
||||
'FriendlyName' => 'Swap开关',
|
||||
'Type' => 'dropdown',
|
||||
'Options' => 'true,false',
|
||||
'Default' => 'true',
|
||||
'Description' => '虚拟内存开关',
|
||||
],
|
||||
'privileged' => [
|
||||
'FriendlyName' => '特权模式',
|
||||
'Type' => 'dropdown',
|
||||
'Options' => 'true,false',
|
||||
'Default' => 'false',
|
||||
'Description' => '特权容器开关',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function lxdapiserver_CreateAccount(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
|
||||
$password = lxdapiserver_generate_password();
|
||||
|
||||
$containerName = 'lxd11451' . $params['serviceid'];
|
||||
|
||||
$cpu = !empty($params['configoptions']['cpus']) ? $params['configoptions']['cpus'] : $params['configoption1'];
|
||||
$memory = !empty($params['configoptions']['memory']) ? $params['configoptions']['memory'] : $params['configoption2'];
|
||||
$disk = !empty($params['configoptions']['disk']) ? $params['configoptions']['disk'] : $params['configoption3'];
|
||||
$image = !empty($params['configoptions']['image']) ? $params['configoptions']['image'] : $params['configoption4'];
|
||||
$ingress = !empty($params['configoptions']['ingress']) ? $params['configoptions']['ingress'] : $params['configoption5'];
|
||||
$egress = !empty($params['configoptions']['egress']) ? $params['configoptions']['egress'] : $params['configoption6'];
|
||||
$traffic_limit = !empty($params['configoptions']['traffic_limit']) ? $params['configoptions']['traffic_limit'] : $params['configoption7'];
|
||||
$ipv4_pool_limit = !empty($params['configoptions']['ipv4_pool_limit']) ? $params['configoptions']['ipv4_pool_limit'] : $params['configoption8'];
|
||||
$ipv4_mapping_limit = !empty($params['configoptions']['ipv4_mapping_limit']) ? $params['configoptions']['ipv4_mapping_limit'] : $params['configoption9'];
|
||||
$ipv6_pool_limit = !empty($params['configoptions']['ipv6_pool_limit']) ? $params['configoptions']['ipv6_pool_limit'] : $params['configoption10'];
|
||||
$ipv6_mapping_limit = !empty($params['configoptions']['ipv6_mapping_limit']) ? $params['configoptions']['ipv6_mapping_limit'] : $params['configoption11'];
|
||||
$reverse_proxy_limit = !empty($params['configoptions']['reverse_proxy_limit']) ? $params['configoptions']['reverse_proxy_limit'] : $params['configoption12'];
|
||||
$cpu_allowance = !empty($params['configoptions']['cpu_allowance']) ? $params['configoptions']['cpu_allowance'] : $params['configoption13'];
|
||||
$io_read = !empty($params['configoptions']['io_read']) ? $params['configoptions']['io_read'] : $params['configoption14'];
|
||||
$io_write = !empty($params['configoptions']['io_write']) ? $params['configoptions']['io_write'] : $params['configoption15'];
|
||||
$processes_limit = !empty($params['configoptions']['processes_limit']) ? $params['configoptions']['processes_limit'] : $params['configoption16'];
|
||||
$allow_nesting = !empty($params['configoptions']['allow_nesting']) ? $params['configoptions']['allow_nesting'] === 'true' : $params['configoption17'] === 'true';
|
||||
$memory_swap = !empty($params['configoptions']['memory_swap']) ? $params['configoptions']['memory_swap'] === 'true' : $params['configoption18'] === 'true';
|
||||
$privileged = !empty($params['configoptions']['privileged']) ? $params['configoptions']['privileged'] === 'true' : $params['configoption19'] === 'true';
|
||||
|
||||
$requestData = [
|
||||
'name' => $containerName,
|
||||
'image' => $image,
|
||||
'username' => 'user_' . $params['clientsdetails']['userid'],
|
||||
'password' => $password,
|
||||
'cpu' => (int)$cpu,
|
||||
'memory' => (int)$memory,
|
||||
'disk' => (int)$disk,
|
||||
'ingress' => (int)$ingress,
|
||||
'egress' => (int)$egress,
|
||||
'traffic_limit' => (int)$traffic_limit,
|
||||
'ipv4_pool_limit' => (int)$ipv4_pool_limit,
|
||||
'ipv4_mapping_limit' => (int)$ipv4_mapping_limit,
|
||||
'ipv6_pool_limit' => (int)$ipv6_pool_limit,
|
||||
'ipv6_mapping_limit' => (int)$ipv6_mapping_limit,
|
||||
'reverse_proxy_limit' => (int)$reverse_proxy_limit,
|
||||
'cpu_allowance' => (int)$cpu_allowance,
|
||||
'io_read' => (int)$io_read,
|
||||
'io_write' => (int)$io_write,
|
||||
'processes_limit' => (int)$processes_limit,
|
||||
'allow_nesting' => $allow_nesting,
|
||||
'memory_swap' => $memory_swap,
|
||||
'privileged' => $privileged,
|
||||
];
|
||||
|
||||
$result = $api->post('/api/system/containers', $requestData);
|
||||
|
||||
if (!$result['success']) {
|
||||
return $result['message'] ?? '创建容器失败';
|
||||
}
|
||||
|
||||
lxdapiserver_save_password($params['serviceid'], $password);
|
||||
|
||||
$updateData = [
|
||||
'domain' => $containerName,
|
||||
];
|
||||
|
||||
if (!empty($result['data']['ipv4'])) {
|
||||
$updateData['dedicatedip'] = $result['data']['ipv4'];
|
||||
}
|
||||
|
||||
Capsule::table('tblhosting')
|
||||
->where('id', $params['serviceid'])
|
||||
->update($updateData);
|
||||
|
||||
return 'success';
|
||||
|
||||
} catch (Exception $e) {
|
||||
lxdapiserver_log_error('CreateAccount', $e->getMessage());
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_SuspendAccount(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->post('/api/system/containers/pause?name=' . urlencode($params['domain']), []);
|
||||
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '暂停失败');
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_UnsuspendAccount(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->post('/api/system/containers/resume?name=' . urlencode($params['domain']), []);
|
||||
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '恢复失败');
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_TerminateAccount(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->delete('/api/system/containers?name=' . urlencode($params['domain']));
|
||||
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '删除失败');
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_ChangePassword(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
|
||||
$result = $api->post('/api/system/containers/reset-password', [
|
||||
'name' => $params['domain'],
|
||||
'password' => $params['password']
|
||||
]);
|
||||
|
||||
if ($result['success']) {
|
||||
lxdapiserver_save_password($params['serviceid'], $params['password']);
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return $result['message'] ?? '修改密码失败';
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_ClientArea(array $params)
|
||||
{
|
||||
$page = $_GET['page'] ?? '';
|
||||
|
||||
if ($page !== 'manage') {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
|
||||
$result = $api->post('/api/system/containers/access-code', [
|
||||
'container_name' => $params['domain']
|
||||
]);
|
||||
|
||||
$jumpUrl = '';
|
||||
$iframeUrl = '';
|
||||
$errorMsg = '';
|
||||
|
||||
if ($result['success'] && !empty($result['data']['access_code'])) {
|
||||
$accessCode = $result['data']['access_code'];
|
||||
$protocol = 'https';
|
||||
$baseUrl = $protocol . '://' . $params['serverip'] . ':' . $params['serverport'];
|
||||
$jumpUrl = $baseUrl . '/container/dashboard?hash=' . $accessCode;
|
||||
$iframeUrl = $baseUrl . '/container/dashboard/lite?hash=' . $accessCode;
|
||||
} else {
|
||||
$errorMsg = $result['message'] ?? '获取访问码失败';
|
||||
}
|
||||
|
||||
return [
|
||||
'tabOverviewReplacementTemplate' => 'templates/overview.tpl',
|
||||
'templateVariables' => [
|
||||
'jump_url' => $jumpUrl,
|
||||
'iframe_url' => $iframeUrl,
|
||||
'error_msg' => $errorMsg,
|
||||
],
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'tabOverviewReplacementTemplate' => 'templates/overview.tpl',
|
||||
'templateVariables' => [
|
||||
'error_msg' => $e->getMessage(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_AdminCustomButtonArray()
|
||||
{
|
||||
return [
|
||||
'开机' => 'start',
|
||||
'关机' => 'stop',
|
||||
'重启' => 'reboot',
|
||||
'重装系统' => 'reinstall',
|
||||
'同步状态' => 'sync',
|
||||
'重置流量' => 'traffic_reset',
|
||||
];
|
||||
}
|
||||
|
||||
function lxdapiserver_start(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->post('/api/system/containers/start?name=' . urlencode($params['domain']), []);
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '开机失败');
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_stop(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->post('/api/system/containers/stop?name=' . urlencode($params['domain']), []);
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '关机失败');
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_reboot(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->post('/api/system/containers/restart?name=' . urlencode($params['domain']), []);
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '重启失败');
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_reinstall(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$password = lxdapiserver_generate_password();
|
||||
|
||||
$result = $api->post('/api/system/containers/reinstall?name=' . urlencode($params['domain']), [
|
||||
'image' => $params['configoption4'],
|
||||
'password' => $password
|
||||
]);
|
||||
|
||||
if ($result['success']) {
|
||||
lxdapiserver_save_password($params['serviceid'], $password);
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return $result['message'] ?? '重装失败';
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_sync(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->get('/api/system/containers/status?name=' . urlencode($params['domain']));
|
||||
|
||||
if ($result['success'] && !empty($result['data'])) {
|
||||
$updateData = [];
|
||||
|
||||
if (!empty($result['data']['ipv4'])) {
|
||||
$updateData['dedicatedip'] = $result['data']['ipv4'];
|
||||
}
|
||||
|
||||
if (isset($result['data']['status'])) {
|
||||
$status = strtoupper($result['data']['status']);
|
||||
if ($status === 'RUNNING') {
|
||||
$updateData['domainstatus'] = 'Active';
|
||||
} elseif ($status === 'STOPPED') {
|
||||
$updateData['domainstatus'] = 'Suspended';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($updateData)) {
|
||||
Capsule::table('tblhosting')
|
||||
->where('id', $params['serviceid'])
|
||||
->update($updateData);
|
||||
}
|
||||
|
||||
return 'success';
|
||||
}
|
||||
|
||||
return $result['message'] ?? '同步失败';
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_traffic_reset(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->post('/api/system/traffic/reset?name=' . urlencode($params['domain']), []);
|
||||
return $result['success'] ? 'success' : ($result['message'] ?? '重置流量失败');
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function lxdapiserver_AdminServicesTabFields(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
$result = $api->get('/api/system/containers/status?name=' . urlencode($params['domain']));
|
||||
|
||||
if ($result['success'] && !empty($result['data'])) {
|
||||
$data = $result['data'];
|
||||
return [
|
||||
'容器状态' => $data['status'] ?? 'Unknown',
|
||||
'IPv4地址' => $data['ipv4'] ?? 'N/A',
|
||||
'IPv6地址' => $data['ipv6'] ?? 'N/A',
|
||||
'CPU使用率' => isset($data['cpu_percent']) ? number_format($data['cpu_percent'], 2) . '%' : 'N/A',
|
||||
'内存使用' => $data['memory_usage'] ?? 'N/A',
|
||||
'硬盘使用' => $data['disk_usage'] ?? 'N/A',
|
||||
'流量使用' => $data['traffic_usage'] ?? 'N/A',
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
lxdapiserver_log_error('AdminServicesTabFields', $e->getMessage());
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function lxdapiserver_ServiceSingleSignOn(array $params)
|
||||
{
|
||||
try {
|
||||
$api = new LXD_API($params);
|
||||
|
||||
$result = $api->post('/api/system/console/create-token', [
|
||||
'hostname' => $params['domain'],
|
||||
]);
|
||||
|
||||
if ($result['success'] && !empty($result['data']['token'])) {
|
||||
$consoleUrl = 'https://' . $params['serverip'] . ':' . $params['serverport'] . '/console?token=' . $result['data']['token'];
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'redirectTo' => $consoleUrl,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'errorMsg' => $result['message'] ?? '创建控制台会话失败',
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'errorMsg' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
68
whmcs/lxdapiserver/templates/overview.tpl
Normal file
68
whmcs/lxdapiserver/templates/overview.tpl
Normal file
@@ -0,0 +1,68 @@
|
||||
<div class="container-fluid">
|
||||
<div class="card shadow mb-3">
|
||||
<div class="card-header py-2">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fa fa-external-link mr-2"></i>容器面板
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body py-3">
|
||||
{if $error_msg}
|
||||
<div class="alert alert-danger">
|
||||
<i class="fa fa-exclamation-triangle"></i> {$error_msg}
|
||||
</div>
|
||||
{elseif $jump_url}
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="{$jump_url}"
|
||||
target="_blank"
|
||||
class="btn btn-primary px-4">
|
||||
<i class="fa fa-external-link mr-2"></i>进入面板
|
||||
</a>
|
||||
<span class="ml-3 text-muted" style="font-size: 13px;">
|
||||
<i class="fa fa-info-circle mr-1"></i>
|
||||
点击按钮将在新窗口打开容器管理面板,也可以通过下方容器管理进行管理。
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if $iframe_url}
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-2">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fa fa-desktop mr-2"></i>容器管理
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<iframe
|
||||
id="container-iframe"
|
||||
src="{$iframe_url}"
|
||||
style="width: 100%; height: 100vh; border: none; display: block;"
|
||||
frameborder="0"
|
||||
scrolling="auto">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
border: none;
|
||||
}
|
||||
.card-header {
|
||||
background-color: #f8f9fc;
|
||||
border-bottom: 1px solid #e3e6f0;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #2563eb;
|
||||
border-color: #2563eb;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #1d4ed8;
|
||||
border-color: #1d4ed8;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user