added website and fixed server
This commit is contained in:
@@ -59,7 +59,9 @@ function callCyberPanelApi(string $panelUrl, string $endpoint, string $username,
|
|||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'adminUser' => $username,
|
'adminUser' => $username,
|
||||||
'adminPass' => $password
|
'adminPass' => $password,
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password
|
||||||
];
|
];
|
||||||
|
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
@@ -165,7 +167,27 @@ try {
|
|||||||
|
|
||||||
$versionResult = callCyberPanelApi($panelUrl, 'cyberPanelVersion', $username, $password);
|
$versionResult = callCyberPanelApi($panelUrl, 'cyberPanelVersion', $username, $password);
|
||||||
$packagesResult = callCyberPanelApi($panelUrl, 'listPackage', $username, $password);
|
$packagesResult = callCyberPanelApi($panelUrl, 'listPackage', $username, $password);
|
||||||
|
if (
|
||||||
|
!$versionResult['success'] &&
|
||||||
|
!$packagesResult['success']
|
||||||
|
) {
|
||||||
|
$failUpdate = $db->prepare("
|
||||||
|
UPDATE cyberpanel_servers
|
||||||
|
SET
|
||||||
|
last_status = 'offline',
|
||||||
|
last_check = NOW()
|
||||||
|
WHERE id = :id
|
||||||
|
");
|
||||||
|
|
||||||
|
$failUpdate->execute([
|
||||||
|
':id' => $serverId
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonResponse(false, 'CyberPanel API authentication failed.', [
|
||||||
|
'version_response' => $versionResult,
|
||||||
|
'packages_response' => $packagesResult
|
||||||
|
]);
|
||||||
|
}
|
||||||
$version = '-';
|
$version = '-';
|
||||||
$packagesCount = 0;
|
$packagesCount = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,452 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../class/db-functions.php';
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
function jsonResponse(bool $success, string $message, array $extra = []): void
|
||||||
|
{
|
||||||
|
echo json_encode(array_merge([
|
||||||
|
'success' => $success,
|
||||||
|
'message' => $message
|
||||||
|
], $extra));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCyberpanelEncryptionKey(): string
|
||||||
|
{
|
||||||
|
return hash('sha256', 'CHANGE_THIS_SECRET_KEY_FOR_CYBERPANEL_DASHBOARD');
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptCyberpanelPassword(string $encryptedPassword): string
|
||||||
|
{
|
||||||
|
if ($encryptedPassword === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = getCyberpanelEncryptionKey();
|
||||||
|
$decoded = base64_decode($encryptedPassword);
|
||||||
|
|
||||||
|
if (!$decoded || strpos($decoded, '::') === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
[$ivBase64, $encrypted] = explode('::', $decoded, 2);
|
||||||
|
$iv = base64_decode($ivBase64);
|
||||||
|
|
||||||
|
if (!$iv) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$decrypted = openssl_decrypt(
|
||||||
|
$encrypted,
|
||||||
|
'AES-256-CBC',
|
||||||
|
$key,
|
||||||
|
0,
|
||||||
|
$iv
|
||||||
|
);
|
||||||
|
|
||||||
|
return $decrypted ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCookieFromJar(string $cookieFile, string $cookieName): string
|
||||||
|
{
|
||||||
|
if (!file_exists($cookieFile)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = file($cookieFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (strpos($line, '#') === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = preg_split('/\s+/', $line);
|
||||||
|
|
||||||
|
if (count($parts) >= 7 && $parts[5] === $cookieName) {
|
||||||
|
return $parts[6];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function cyberpanelGet(string $url, string $cookieFile): array
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HEADER => false,
|
||||||
|
CURLOPT_COOKIEJAR => $cookieFile,
|
||||||
|
CURLOPT_COOKIEFILE => $cookieFile,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 15,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
CURLOPT_USERAGENT => 'Mozilla/5.0 CyberPanelDashboard'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'http_code' => $httpCode,
|
||||||
|
'curl_error' => $curlError,
|
||||||
|
'raw' => $response
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function cyberpanelPostJson(string $url, array $payload, string $cookieFile, string $csrfToken, string $referer): array
|
||||||
|
{
|
||||||
|
$headers = [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: application/json',
|
||||||
|
'X-Csrftoken: ' . $csrfToken,
|
||||||
|
'Referer: ' . $referer
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HEADER => false,
|
||||||
|
CURLOPT_COOKIEJAR => $cookieFile,
|
||||||
|
CURLOPT_COOKIEFILE => $cookieFile,
|
||||||
|
CURLOPT_TIMEOUT => 30,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 15,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
CURLOPT_USERAGENT => 'Mozilla/5.0 CyberPanelDashboard'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$json = json_decode((string)$response, true);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'http_code' => $httpCode,
|
||||||
|
'curl_error' => $curlError,
|
||||||
|
'raw' => $response,
|
||||||
|
'json' => is_array($json) ? $json : null
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeWebsiteItems(array $fetchJson): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
|
||||||
|
if (isset($fetchJson['data'])) {
|
||||||
|
if (is_string($fetchJson['data'])) {
|
||||||
|
$decodedData = json_decode($fetchJson['data'], true);
|
||||||
|
if (is_array($decodedData)) {
|
||||||
|
$items = $decodedData;
|
||||||
|
}
|
||||||
|
} elseif (is_array($fetchJson['data'])) {
|
||||||
|
$items = $fetchJson['data'];
|
||||||
|
}
|
||||||
|
} elseif (isset($fetchJson['websites']) && is_array($fetchJson['websites'])) {
|
||||||
|
$items = $fetchJson['websites'];
|
||||||
|
} elseif (isset($fetchJson['records']) && is_array($fetchJson['records'])) {
|
||||||
|
$items = $fetchJson['records'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$websites = [];
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (!is_array($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = $item['domain'] ?? $item['domainName'] ?? $item['website'] ?? $item['name'] ?? '';
|
||||||
|
$owner = $item['admin'] ?? $item['owner'] ?? $item['user'] ?? $item['websiteOwner'] ?? '';
|
||||||
|
$ownerUsername = $item['owner_username'] ?? $item['ownerUsername'] ?? $item['username'] ?? $owner ?? '';
|
||||||
|
$package = $item['package'] ?? $item['packageName'] ?? '';
|
||||||
|
$phpVersion = $item['php_version'] ?? $item['phpVersion'] ?? $item['php'] ?? '';
|
||||||
|
$sslStatus = $item['ssl']['status'] ?? $item['ssl_status'] ?? $item['sslStatus'] ?? 'unknown';
|
||||||
|
$diskUsage = $item['diskUsed'] ?? $item['disk_usage'] ?? $item['diskUsage'] ?? '';
|
||||||
|
$status = $item['status'] ?? $item['state'] ?? 'unknown';
|
||||||
|
|
||||||
|
if ($domain === '') {
|
||||||
|
foreach ($item as $value) {
|
||||||
|
if (is_string($value) && preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/i', trim($value))) {
|
||||||
|
$domain = trim($value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($domain !== '') {
|
||||||
|
$websites[] = [
|
||||||
|
'domain' => $domain,
|
||||||
|
'owner' => $owner,
|
||||||
|
'owner_username' => $ownerUsername,
|
||||||
|
'package' => $package,
|
||||||
|
'php_version' => $phpVersion,
|
||||||
|
'ssl_status' => $sslStatus,
|
||||||
|
'disk_usage' => $diskUsage,
|
||||||
|
'status' => strtolower(trim((string)$status)),
|
||||||
|
'php_version' => trim((string)$phpVersion),
|
||||||
|
'ssl_status' => strtolower(trim((string)$sslStatus)),
|
||||||
|
'disk_usage' => trim((string)$diskUsage),
|
||||||
|
'raw' => $item
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $websites;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$serverId = isset($_POST['server_id']) ? (int)$_POST['server_id'] : 0;
|
||||||
|
|
||||||
|
if ($serverId <= 0) {
|
||||||
|
jsonResponse(false, 'Missing server ID.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
panel_url,
|
||||||
|
username,
|
||||||
|
password_encrypted,
|
||||||
|
api_enabled
|
||||||
|
FROM cyberpanel_servers
|
||||||
|
WHERE id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $serverId
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$server) {
|
||||||
|
jsonResponse(false, 'Server not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int)$server['api_enabled'] !== 1) {
|
||||||
|
jsonResponse(false, 'API disabled for this server.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$panelUrl = rtrim($server['panel_url'], '/');
|
||||||
|
$username = $server['username'];
|
||||||
|
$password = decryptCyberpanelPassword($server['password_encrypted']);
|
||||||
|
|
||||||
|
if ($password === '') {
|
||||||
|
jsonResponse(false, 'Password cannot be decrypted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$cookieFile = tempnam(sys_get_temp_dir(), 'cyberpanel_cookie_');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Step 1: open CyberPanel first to obtain csrftoken cookie.
|
||||||
|
*/
|
||||||
|
$initialGet = cyberpanelGet($panelUrl . '/', $cookieFile);
|
||||||
|
|
||||||
|
if ($initialGet['curl_error'] !== '') {
|
||||||
|
@unlink($cookieFile);
|
||||||
|
jsonResponse(false, 'CyberPanel initial GET failed: ' . $initialGet['curl_error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrfToken = readCookieFromJar($cookieFile, 'csrftoken');
|
||||||
|
|
||||||
|
if ($csrfToken === '') {
|
||||||
|
@unlink($cookieFile);
|
||||||
|
jsonResponse(false, 'CSRF token not found from CyberPanel.', [
|
||||||
|
'http_code' => $initialGet['http_code'],
|
||||||
|
'raw_preview' => substr(strip_tags((string)$initialGet['raw']), 0, 1000)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Step 2: login with csrf token.
|
||||||
|
*/
|
||||||
|
$login = cyberpanelPostJson(
|
||||||
|
$panelUrl . '/verifyLogin',
|
||||||
|
[
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password,
|
||||||
|
'languageSelection' => 'english'
|
||||||
|
],
|
||||||
|
$cookieFile,
|
||||||
|
$csrfToken,
|
||||||
|
$panelUrl . '/'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($login['curl_error'] !== '') {
|
||||||
|
@unlink($cookieFile);
|
||||||
|
jsonResponse(false, 'CyberPanel login cURL error: ' . $login['curl_error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$login['json']) {
|
||||||
|
@unlink($cookieFile);
|
||||||
|
jsonResponse(false, 'CyberPanel login did not return JSON.', [
|
||||||
|
'http_code' => $login['http_code'],
|
||||||
|
'raw_preview' => substr(strip_tags((string)$login['raw']), 0, 1000)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$loginOk = false;
|
||||||
|
|
||||||
|
if (isset($login['json']['loginStatus']) && $login['json']['loginStatus']) {
|
||||||
|
$loginOk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($login['json']['status']) && (int)$login['json']['status'] === 1) {
|
||||||
|
$loginOk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$loginOk) {
|
||||||
|
@unlink($cookieFile);
|
||||||
|
jsonResponse(false, 'CyberPanel login failed.', [
|
||||||
|
'login_response' => $login['json']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Step 3: after login, read sessionid and fetch websites.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
$csrfTokenAfterLogin = readCookieFromJar($cookieFile, 'csrftoken');
|
||||||
|
|
||||||
|
if ($csrfTokenAfterLogin !== '') {
|
||||||
|
$csrfToken = $csrfTokenAfterLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fetch = cyberpanelPostJson(
|
||||||
|
$panelUrl . '/websites/fetchWebsitesList',
|
||||||
|
[
|
||||||
|
'page' => 1,
|
||||||
|
'recordsToShow' => 1000
|
||||||
|
],
|
||||||
|
$cookieFile,
|
||||||
|
$csrfToken,
|
||||||
|
$panelUrl . '/websites/listWebsites'
|
||||||
|
);
|
||||||
|
|
||||||
|
@unlink($cookieFile);
|
||||||
|
|
||||||
|
if ($fetch['curl_error'] !== '') {
|
||||||
|
jsonResponse(false, 'fetchWebsitesList cURL error: ' . $fetch['curl_error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$fetch['json']) {
|
||||||
|
jsonResponse(false, 'fetchWebsitesList did not return JSON.', [
|
||||||
|
'http_code' => $fetch['http_code'],
|
||||||
|
'raw_preview' => substr(strip_tags((string)$fetch['raw']), 0, 1000)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isset($fetch['json']['error_message']) &&
|
||||||
|
stripos($fetch['json']['error_message'], 'session') !== false
|
||||||
|
) {
|
||||||
|
jsonResponse(false, 'CyberPanel session not accepted.', [
|
||||||
|
'fetch_response' => $fetch['json']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$websites = normalizeWebsiteItems($fetch['json']);
|
||||||
|
|
||||||
|
$upsert = $db->prepare("
|
||||||
|
INSERT INTO cyberpanel_websites
|
||||||
|
(
|
||||||
|
server_id,
|
||||||
|
domain,
|
||||||
|
website_owner,
|
||||||
|
owner_username,
|
||||||
|
package_name,
|
||||||
|
php_version,
|
||||||
|
ssl_status,
|
||||||
|
website_status,
|
||||||
|
disk_usage,
|
||||||
|
raw_json,
|
||||||
|
last_sync
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
:server_id,
|
||||||
|
:domain,
|
||||||
|
:website_owner,
|
||||||
|
:owner_username,
|
||||||
|
:package_name,
|
||||||
|
:php_version,
|
||||||
|
:ssl_status,
|
||||||
|
:website_status,
|
||||||
|
:disk_usage,
|
||||||
|
:raw_json,
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
website_owner = VALUES(website_owner),
|
||||||
|
owner_username = VALUES(owner_username),
|
||||||
|
package_name = VALUES(package_name),
|
||||||
|
php_version = VALUES(php_version),
|
||||||
|
ssl_status = VALUES(ssl_status),
|
||||||
|
website_status = VALUES(website_status),
|
||||||
|
disk_usage = VALUES(disk_usage),
|
||||||
|
raw_json = VALUES(raw_json),
|
||||||
|
last_sync = NOW()
|
||||||
|
");
|
||||||
|
|
||||||
|
$inserted = 0;
|
||||||
|
|
||||||
|
$inserted = 0;
|
||||||
|
|
||||||
|
foreach ($websites as $website) {
|
||||||
|
$upsert->execute([
|
||||||
|
':server_id' => $serverId,
|
||||||
|
':domain' => $website['domain'],
|
||||||
|
':website_owner' => $website['owner'],
|
||||||
|
':owner_username' => $website['owner_username'] ?: null,
|
||||||
|
':package_name' => $website['package'] ?: null,
|
||||||
|
':php_version' => $website['php_version'] ?: null,
|
||||||
|
':ssl_status' => $website['ssl_status'] ?: 'unknown',
|
||||||
|
':website_status' => $website['status'] ?: 'unknown',
|
||||||
|
':disk_usage' => $website['disk_usage'] ?: null,
|
||||||
|
':raw_json' => json_encode($website['raw'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$inserted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$update = $db->prepare("
|
||||||
|
UPDATE cyberpanel_servers
|
||||||
|
SET
|
||||||
|
last_status = 'online',
|
||||||
|
last_check = NOW()
|
||||||
|
WHERE id = :server_id
|
||||||
|
");
|
||||||
|
|
||||||
|
$update->execute([
|
||||||
|
':server_id' => $serverId
|
||||||
|
]);
|
||||||
|
|
||||||
|
jsonResponse(true, 'Websites synced successfully.', [
|
||||||
|
'count' => $inserted,
|
||||||
|
'websites' => $websites,
|
||||||
|
'raw_response' => $fetch['json']
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
jsonResponse(false, 'Sync websites error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../class/db-functions.php';
|
||||||
|
|
||||||
|
$db = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
function jsonResponse(bool $success, string $message, array $extra = []): void
|
||||||
|
{
|
||||||
|
echo json_encode(array_merge([
|
||||||
|
'success' => $success,
|
||||||
|
'message' => $message
|
||||||
|
], $extra));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCyberpanelEncryptionKey(): string
|
||||||
|
{
|
||||||
|
return hash('sha256', 'CHANGE_THIS_SECRET_KEY_FOR_CYBERPANEL_DASHBOARD');
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptCyberpanelPassword(string $encryptedPassword): string
|
||||||
|
{
|
||||||
|
if ($encryptedPassword === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = getCyberpanelEncryptionKey();
|
||||||
|
$decoded = base64_decode($encryptedPassword);
|
||||||
|
|
||||||
|
if (!$decoded || strpos($decoded, '::') === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
[$ivBase64, $encrypted] = explode('::', $decoded, 2);
|
||||||
|
$iv = base64_decode($ivBase64);
|
||||||
|
|
||||||
|
if (!$iv) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$decrypted = openssl_decrypt(
|
||||||
|
$encrypted,
|
||||||
|
'AES-256-CBC',
|
||||||
|
$key,
|
||||||
|
0,
|
||||||
|
$iv
|
||||||
|
);
|
||||||
|
|
||||||
|
return $decrypted ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function callCyberPanel(string $panelUrl, string $endpoint, string $username, string $password, array $payload = []): array
|
||||||
|
{
|
||||||
|
$url = rtrim($panelUrl, '/') . '/' . ltrim($endpoint, '/');
|
||||||
|
|
||||||
|
$payload = array_merge([
|
||||||
|
'adminUser' => $username,
|
||||||
|
'adminPass' => $password
|
||||||
|
], $payload);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Accept: application/json'
|
||||||
|
],
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 25,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 15,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$curlError = curl_error($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
$json = json_decode($response, true);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'url' => $url,
|
||||||
|
'http_code' => $httpCode,
|
||||||
|
'curl_error' => $curlError,
|
||||||
|
'is_json' => is_array($json),
|
||||||
|
'json' => $json,
|
||||||
|
'raw_preview' => substr(strip_tags((string)$response), 0, 700)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$serverId = isset($_POST['server_id']) ? (int)$_POST['server_id'] : 0;
|
||||||
|
|
||||||
|
if ($serverId <= 0) {
|
||||||
|
jsonResponse(false, 'Missing server ID.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
panel_url,
|
||||||
|
username,
|
||||||
|
password_encrypted
|
||||||
|
FROM cyberpanel_servers
|
||||||
|
WHERE id = :id
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
':id' => $serverId
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$server) {
|
||||||
|
jsonResponse(false, 'Server not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$password = decryptCyberpanelPassword($server['password_encrypted']);
|
||||||
|
|
||||||
|
if ($password === '') {
|
||||||
|
jsonResponse(false, 'Password cannot be decrypted.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$tests = [];
|
||||||
|
|
||||||
|
$tests[] = callCyberPanel(
|
||||||
|
$server['panel_url'],
|
||||||
|
'websites/fetchWebsitesList',
|
||||||
|
$server['username'],
|
||||||
|
$password
|
||||||
|
);
|
||||||
|
|
||||||
|
$tests[] = callCyberPanel(
|
||||||
|
$server['panel_url'],
|
||||||
|
'websites/listWebsites',
|
||||||
|
$server['username'],
|
||||||
|
$password
|
||||||
|
);
|
||||||
|
|
||||||
|
$tests[] = callCyberPanel(
|
||||||
|
$server['panel_url'],
|
||||||
|
'api/listWebsites',
|
||||||
|
$server['username'],
|
||||||
|
$password
|
||||||
|
);
|
||||||
|
|
||||||
|
jsonResponse(true, 'Website API test completed.', [
|
||||||
|
'tests' => $tests
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
jsonResponse(false, 'Website API test error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
@@ -102,38 +102,7 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-card {
|
|
||||||
border: 1px solid #edf0f4;
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 18px;
|
|
||||||
background: #fff;
|
|
||||||
height: 100%;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.server-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.server-main {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.server-name {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 17px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.server-url {
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 13px;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -185,20 +154,7 @@
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.server-actions .btn {
|
|
||||||
border-radius: 9px;
|
|
||||||
font-size: 13px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.domain-table th {
|
.domain-table th {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -282,6 +238,150 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-card {
|
||||||
|
border: 1px solid #e8edf3;
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(180deg, #ffffff 0%, #fbfcfe 100%);
|
||||||
|
height: 100%;
|
||||||
|
transition: all 0.22s ease;
|
||||||
|
box-shadow: 0 6px 18px rgba(16, 24, 40, 0.04);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 auto 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #0d6efd 0%, #6ea8fe 100%);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 16px 36px rgba(16, 24, 40, 0.08);
|
||||||
|
border-color: #d8e2f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-main {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-url {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 13px;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-meta-item {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #eef2f7;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-meta-label {
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-meta-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0f172a;
|
||||||
|
line-height: 1.3;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-box {
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #eef2f7;
|
||||||
|
padding: 14px 12px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #0f172a;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px dashed #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-lastcheck {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-actions .btn {
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -314,10 +414,13 @@
|
|||||||
environment,
|
environment,
|
||||||
notes,
|
notes,
|
||||||
last_check,
|
last_check,
|
||||||
last_status
|
last_status,
|
||||||
|
cyberpanel_version,
|
||||||
|
packages_count,
|
||||||
|
websites_count
|
||||||
FROM cyberpanel_servers
|
FROM cyberpanel_servers
|
||||||
ORDER BY name ASC
|
ORDER BY name ASC
|
||||||
";
|
";
|
||||||
|
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
|
|
||||||
@@ -339,9 +442,11 @@
|
|||||||
'cpu' => 0,
|
'cpu' => 0,
|
||||||
'ram' => 0,
|
'ram' => 0,
|
||||||
'disk' => 0,
|
'disk' => 0,
|
||||||
'websites' => 0,
|
'websites' => (int)($row['websites_count'] ?? 0),
|
||||||
|
'packages_count' => (int)($row['packages_count'] ?? 0),
|
||||||
'databases' => 0,
|
'databases' => 0,
|
||||||
'ssl_expiring' => 0,
|
'sslexpiring' => 0,
|
||||||
|
'cyberpanel_version' => $row['cyberpanel_version'] ?? '-',
|
||||||
'last_check' => $row['last_check'] ?: '-'
|
'last_check' => $row['last_check'] ?: '-'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -377,6 +482,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$websites = [];
|
||||||
|
|
||||||
|
$stmtWebsites = $db->query("
|
||||||
|
SELECT
|
||||||
|
w.id,
|
||||||
|
w.server_id,
|
||||||
|
w.domain,
|
||||||
|
w.website_owner,
|
||||||
|
w.owner_username,
|
||||||
|
w.package_name,
|
||||||
|
w.php_version,
|
||||||
|
w.ssl_status,
|
||||||
|
w.disk_usage,
|
||||||
|
w.website_status,
|
||||||
|
w.last_sync,
|
||||||
|
s.name AS server_name,
|
||||||
|
s.panel_url
|
||||||
|
FROM cyberpanel_websites w
|
||||||
|
LEFT JOIN cyberpanel_servers s ON s.id = w.server_id
|
||||||
|
ORDER BY s.name ASC, w.domain ASC
|
||||||
|
");
|
||||||
|
|
||||||
|
if ($stmtWebsites) {
|
||||||
|
while ($rowWebsite = $stmtWebsites->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$websites[] = $rowWebsite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$domains = [
|
$domains = [
|
||||||
[
|
[
|
||||||
'domain' => 'yogasoul.it',
|
'domain' => 'yogasoul.it',
|
||||||
@@ -577,87 +710,76 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-2">
|
<div class="server-meta-grid">
|
||||||
<div class="col-4">
|
<div class="server-meta-item">
|
||||||
<small class="text-muted">Provider</small>
|
<div class="server-meta-label">Provider</div>
|
||||||
<div class="fw-bold"><?= htmlspecialchars($server['provider'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></div>
|
<div class="server-meta-value"><?= htmlspecialchars($server['provider'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-4">
|
<div class="server-meta-item">
|
||||||
<small class="text-muted">IP</small>
|
<div class="server-meta-label">IP Address</div>
|
||||||
<div class="fw-bold"><?= htmlspecialchars($server['ip_address'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></div>
|
<div class="server-meta-value"><?= htmlspecialchars($server['ip_address'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-4">
|
<div class="server-meta-item">
|
||||||
<small class="text-muted">Env</small>
|
<div class="server-meta-label">Environment</div>
|
||||||
<div class="fw-bold"><?= htmlspecialchars(ucfirst($server['environment'] ?? 'production'), ENT_QUOTES, 'UTF-8'); ?></div>
|
<div class="server-meta-value"><?= htmlspecialchars(ucfirst($server['environment'] ?? 'production'), ENT_QUOTES, 'UTF-8'); ?></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metric-row">
|
<div class="metric-row">
|
||||||
<div class="metric-box">
|
<div class="metric-box">
|
||||||
<div class="metric-label">CPU</div>
|
<div class="metric-label">CyberPanel</div>
|
||||||
<div class="metric-value"><?= (int)$server['cpu']; ?>%</div>
|
<div class="metric-value"><?= htmlspecialchars($server['cyberpanel_version'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></div>
|
||||||
<div class="progress mt-2">
|
|
||||||
<div class="progress-bar" style="width: <?= (int)$server['cpu']; ?>%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metric-box">
|
<div class="metric-box">
|
||||||
<div class="metric-label">RAM</div>
|
<div class="metric-label">Packages</div>
|
||||||
<div class="metric-value"><?= (int)$server['ram']; ?>%</div>
|
<div class="metric-value"><?= (int)($server['packages_count'] ?? 0); ?></div>
|
||||||
<div class="progress mt-2">
|
|
||||||
<div class="progress-bar bg-success" style="width: <?= (int)$server['ram']; ?>%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metric-box">
|
<div class="metric-box">
|
||||||
<div class="metric-label">Disk</div>
|
<div class="metric-label">Websites</div>
|
||||||
<div class="metric-value"><?= (int)$server['disk']; ?>%</div>
|
<div class="metric-value"><?= (int)($server['websites'] ?? 0); ?></div>
|
||||||
<div class="progress mt-2">
|
|
||||||
<div class="progress-bar bg-warning" style="width: <?= (int)$server['disk']; ?>%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-2">
|
|
||||||
<div class="col-4">
|
|
||||||
<small class="text-muted">Websites</small>
|
|
||||||
<div class="fw-bold"><?= (int)$server['websites']; ?></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-4">
|
|
||||||
<small class="text-muted">Databases</small>
|
|
||||||
<div class="fw-bold"><?= (int)$server['databases']; ?></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-4">
|
|
||||||
<small class="text-muted">API</small>
|
|
||||||
<div class="fw-bold"><?= htmlspecialchars(ucfirst($server['api_status']), ENT_QUOTES, 'UTF-8'); ?></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="server-footer">
|
||||||
<small class="text-muted">
|
<div class="server-lastcheck">
|
||||||
Last check:
|
Last check: <?= htmlspecialchars($server['last_check'] ?: '-', ENT_QUOTES, 'UTF-8'); ?>
|
||||||
<?= htmlspecialchars($server['last_check'], ENT_QUOTES, 'UTF-8'); ?>
|
</div>
|
||||||
</small>
|
|
||||||
|
<div class="server-lastcheck">
|
||||||
|
API: <?= htmlspecialchars(ucfirst($server['api_status']), ENT_QUOTES, 'UTF-8'); ?>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="server-actions">
|
<div class="server-actions">
|
||||||
<a href="<?= htmlspecialchars($server['panel_url'], ENT_QUOTES, 'UTF-8'); ?>" target="_blank" class="btn btn-primary btn-sm">
|
<a href="<?= htmlspecialchars($server['panel_url'], ENT_QUOTES, 'UTF-8'); ?>" target="_blank" class="btn btn-primary btn-sm">
|
||||||
<i class="bx bx-link-external"></i>
|
<i class="bx bx-link-external"></i>
|
||||||
Open CyberPanel
|
Open Panel
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm btn-edit-server"
|
<button type="button" class="btn btn-outline-primary btn-sm btn-load-summary"
|
||||||
|
data-id="<?= (int)$server['id']; ?>">
|
||||||
|
<i class="bx bx-data"></i>
|
||||||
|
Sync Server
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-outline-info btn-sm btn-sync-websites"
|
||||||
|
data-id="<?= (int)$server['id']; ?>">
|
||||||
|
<i class="bx bx-globe"></i>
|
||||||
|
Sync Websites
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm btn-edit-server"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#serverModal"
|
data-bs-target="#serverModal"
|
||||||
data-id="<?= (int)$server['id']; ?>"
|
data-id="<?= (int)$server['id']; ?>"
|
||||||
data-name="<?= htmlspecialchars($server['name'], ENT_QUOTES, 'UTF-8'); ?>"
|
data-name="<?= htmlspecialchars($server['name'], ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
data-url="<?= htmlspecialchars($server['panel_url'], ENT_QUOTES, 'UTF-8'); ?>"
|
data-url="<?= htmlspecialchars($server['panel_url'], ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
data-provider="<?= htmlspecialchars($server['provider'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
data-provider="<?= htmlspecialchars($server['provider'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
data-ip="<?= htmlspecialchars($server['ip_address'] ?? $server['ip'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
data-ip="<?= htmlspecialchars($server['ip_address'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
data-username="<?= htmlspecialchars($server['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
data-username="<?= htmlspecialchars($server['username'] ?? '', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
data-api-enabled="<?= (int)($server['api_enabled'] ?? 1); ?>"
|
data-api-enabled="<?= (int)($server['api_enabled'] ?? 1); ?>"
|
||||||
data-environment="<?= htmlspecialchars($server['environment'] ?? 'production', ENT_QUOTES, 'UTF-8'); ?>"
|
data-environment="<?= htmlspecialchars($server['environment'] ?? 'production', ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
@@ -665,21 +787,7 @@
|
|||||||
<i class="bx bx-edit"></i>
|
<i class="bx bx-edit"></i>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-outline-success btn-sm btn-check-server"
|
|
||||||
data-id="<?= (int)$server['id']; ?>">
|
|
||||||
<i class="bx bx-refresh"></i>
|
|
||||||
Check API
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm btn-server-actions"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#apiActionModal"
|
|
||||||
data-server="<?= htmlspecialchars($server['name'], ENT_QUOTES, 'UTF-8'); ?>">
|
|
||||||
<i class="bx bx-terminal"></i>
|
|
||||||
Actions
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -706,72 +814,89 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Domain</th>
|
<th>Domain</th>
|
||||||
<th>Server</th>
|
<th>Server</th>
|
||||||
|
<th>Owner</th>
|
||||||
|
<th>Package</th>
|
||||||
<th>PHP</th>
|
<th>PHP</th>
|
||||||
<th>SSL</th>
|
<th>SSL</th>
|
||||||
<th>Disk</th>
|
<th>Disk</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
|
<th>Last Sync</th>
|
||||||
<th class="text-end">Actions</th>
|
<th class="text-end">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($domains as $domain): ?>
|
<?php if (empty($websites)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="10" class="text-center text-muted">
|
||||||
|
No websites synced yet.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($websites as $website): ?>
|
||||||
<?php
|
<?php
|
||||||
$domainStatusClass = 'status-online';
|
$status = strtolower($website['website_status'] ?? 'unknown');
|
||||||
if ($domain['status'] === 'Warning') {
|
$statusClass = 'status-warning';
|
||||||
$domainStatusClass = 'status-warning';
|
|
||||||
} elseif ($domain['status'] === 'Offline') {
|
if ($status === 'online' || $status === 'active' || $status === '1') {
|
||||||
$domainStatusClass = 'status-offline';
|
$statusClass = 'status-online';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($status === 'offline' || $status === 'disabled' || $status === '0') {
|
||||||
|
$statusClass = 'status-offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
$ssl = strtolower($website['ssl_status'] ?? 'unknown');
|
||||||
|
$sslClass = 'status-warning';
|
||||||
|
|
||||||
|
if ($ssl === 'valid' || $ssl === 'active' || $ssl === 'ok') {
|
||||||
|
$sslClass = 'status-online';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ssl === 'expired' || $ssl === 'missing' || $ssl === 'invalid') {
|
||||||
|
$sslClass = 'status-offline';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<strong><?= htmlspecialchars($domain['domain'], ENT_QUOTES, 'UTF-8'); ?></strong>
|
<strong><?= htmlspecialchars($website['domain'], ENT_QUOTES, 'UTF-8'); ?></strong>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td><?= htmlspecialchars($domain['server'], ENT_QUOTES, 'UTF-8'); ?></td>
|
<td><?= htmlspecialchars($website['server_name'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-light text-dark">
|
<?= htmlspecialchars($website['website_owner'] ?: ($website['owner_username'] ?: '-'), ENT_QUOTES, 'UTF-8'); ?>
|
||||||
PHP <?= htmlspecialchars($domain['php'], ENT_QUOTES, 'UTF-8'); ?>
|
</td>
|
||||||
|
|
||||||
|
<td><?= htmlspecialchars($website['package_name'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
|
||||||
|
<td><?= htmlspecialchars($website['php_version'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="status-badge <?= $sslClass; ?>">
|
||||||
|
<?= htmlspecialchars(ucfirst($ssl), ENT_QUOTES, 'UTF-8'); ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td><?= htmlspecialchars($domain['ssl'], ENT_QUOTES, 'UTF-8'); ?></td>
|
<td><?= htmlspecialchars($website['disk_usage'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
|
||||||
<td><?= htmlspecialchars($domain['disk'], ENT_QUOTES, 'UTF-8'); ?></td>
|
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge <?= $domainStatusClass; ?>">
|
<span class="status-badge <?= $statusClass; ?>">
|
||||||
<?= htmlspecialchars($domain['status'], ENT_QUOTES, 'UTF-8'); ?>
|
<?= htmlspecialchars(ucfirst($status), ENT_QUOTES, 'UTF-8'); ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<td><?= htmlspecialchars($website['last_sync'] ?: '-', ENT_QUOTES, 'UTF-8'); ?></td>
|
||||||
|
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="btn-group">
|
<a href="https://<?= htmlspecialchars($website['domain'], ENT_QUOTES, 'UTF-8'); ?>" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||||
<a href="<?= htmlspecialchars($domain['panel_url'], ENT_QUOTES, 'UTF-8'); ?>" target="_blank" class="btn btn-sm btn-primary">
|
Open Site
|
||||||
Open Panel
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
|
||||||
<li><a class="dropdown-item" href="javascript:;" data-action="view-domain">View details</a></li>
|
|
||||||
<li><a class="dropdown-item" href="javascript:;" data-action="issue-ssl">Issue SSL</a></li>
|
|
||||||
<li><a class="dropdown-item" href="javascript:;" data-action="change-php">Change PHP version</a></li>
|
|
||||||
<li><a class="dropdown-item" href="javascript:;" data-action="backup-domain">Create backup</a></li>
|
|
||||||
<li>
|
|
||||||
<hr class="dropdown-divider">
|
|
||||||
</li>
|
|
||||||
<li><a class="dropdown-item text-danger" href="javascript:;" data-action="delete-domain">Delete website</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -1105,6 +1230,23 @@
|
|||||||
|
|
||||||
<div class="overlay toggle-icon"></div>
|
<div class="overlay toggle-icon"></div>
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<div class="modal fade" id="syncResultModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Sync Websites</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="syncResultMessage" class="fw-bold mb-2">Operation completed.</div>
|
||||||
|
<div id="syncResultDetails" class="text-muted small"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1126,6 +1268,77 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
document.querySelectorAll(".btn-sync-websites").forEach(function(button) {
|
||||||
|
button.addEventListener("click", function() {
|
||||||
|
const serverId = this.dataset.id;
|
||||||
|
const oldHtml = this.innerHTML;
|
||||||
|
const btn = this;
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<i class="bx bx-loader bx-spin"></i> Syncing...';
|
||||||
|
|
||||||
|
function showSyncResultModal(message, details = '') {
|
||||||
|
const messageEl = document.getElementById('syncResultMessage');
|
||||||
|
const detailsEl = document.getElementById('syncResultDetails');
|
||||||
|
const modalEl = document.getElementById('syncResultModal');
|
||||||
|
|
||||||
|
if (messageEl) messageEl.textContent = message;
|
||||||
|
if (detailsEl) detailsEl.textContent = details;
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(modalEl);
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
appendLog("INFO", "Syncing websites for server ID " + serverId);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("server_id", serverId);
|
||||||
|
|
||||||
|
fetch("ajax/sync_cyberpanel_websites.php", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(function(text) {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("RAW RESPONSE:", text);
|
||||||
|
alert("Invalid JSON from PHP:\n\n" + text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
appendLog("OK", "Websites synced: " + data.count);
|
||||||
|
showSyncResultModal(
|
||||||
|
"Sync completato con successo",
|
||||||
|
"Siti sincronizzati: " + data.count
|
||||||
|
);
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(data);
|
||||||
|
appendLog("ERROR", data.message);
|
||||||
|
alert(data.message);
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error(error);
|
||||||
|
alert("Sync websites error: " + error.message);
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = oldHtml;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
document.querySelectorAll(".btn-edit-server").forEach(function(btn) {
|
document.querySelectorAll(".btn-edit-server").forEach(function(btn) {
|
||||||
btn.addEventListener("click", function() {
|
btn.addEventListener("click", function() {
|
||||||
@@ -1226,83 +1439,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshOneServer(button, showAlert) {
|
|
||||||
const serverId = button.dataset.id;
|
|
||||||
|
|
||||||
if (!serverId) {
|
|
||||||
appendLog("ERROR", "Missing server ID.");
|
|
||||||
return Promise.resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldHtml = button.innerHTML;
|
|
||||||
|
|
||||||
button.disabled = true;
|
|
||||||
button.innerHTML = '<i class="bx bx-loader bx-spin"></i> Checking...';
|
|
||||||
|
|
||||||
appendLog("INFO", "Checking server ID " + serverId);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("server_id", serverId);
|
|
||||||
|
|
||||||
return fetch("ajax/check_cyberpanel_connection.php", {
|
|
||||||
method: "POST",
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(function(response) {
|
|
||||||
return response.text();
|
|
||||||
})
|
|
||||||
.then(function(text) {
|
|
||||||
let data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(text);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("RAW RESPONSE:", text);
|
|
||||||
appendLog("ERROR", "Invalid JSON response from server ID " + serverId);
|
|
||||||
|
|
||||||
if (showAlert) {
|
|
||||||
alert("ERRORE PHP AJAX:\n\n" + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
appendLog("OK", "Server ID " + serverId + " online");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendLog("ERROR", "Server ID " + serverId + " offline: " + data.message);
|
|
||||||
|
|
||||||
if (showAlert) {
|
|
||||||
alert(data.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
console.error(error);
|
|
||||||
appendLog("ERROR", "Unexpected error on server ID " + serverId);
|
|
||||||
|
|
||||||
if (showAlert) {
|
|
||||||
alert("Unexpected error: " + error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.finally(function() {
|
|
||||||
button.disabled = false;
|
|
||||||
button.innerHTML = oldHtml;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll(".btn-check-server").forEach(function(button) {
|
|
||||||
button.addEventListener("click", function() {
|
|
||||||
refreshOneServer(this, true).then(function() {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("btnRefreshAll").addEventListener("click", async function() {
|
document.getElementById("btnRefreshAll").addEventListener("click", async function() {
|
||||||
const refreshAllButton = this;
|
const refreshAllButton = this;
|
||||||
|
|||||||
Reference in New Issue
Block a user