1183 lines
50 KiB
PHP
Executable File
1183 lines
50 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* DNS Manager - FreeDNS + Cloudflare
|
|
* FreeDNS: Multi-comptes (limite 5 domaines/compte)
|
|
* Cloudflare: API complète
|
|
*/
|
|
error_reporting(E_ALL);
|
|
ini_set('display_errors', 0);
|
|
|
|
function getDB() {
|
|
static $pdo = null;
|
|
if ($pdo === null) {
|
|
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123",
|
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
|
}
|
|
return $pdo;
|
|
}
|
|
|
|
function ensureTables() {
|
|
$pdo = getDB();
|
|
|
|
// FreeDNS Accounts (multi-comptes)
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.freedns_accounts (
|
|
id SERIAL PRIMARY KEY,
|
|
username VARCHAR(100) NOT NULL,
|
|
password VARCHAR(255) NOT NULL,
|
|
cookie TEXT,
|
|
domains_count INTEGER DEFAULT 0,
|
|
max_domains INTEGER DEFAULT 5,
|
|
status VARCHAR(50) DEFAULT 'active',
|
|
last_login TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
|
|
// FreeDNS Domains
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.freedns_domains (
|
|
id SERIAL PRIMARY KEY,
|
|
account_id INTEGER REFERENCES admin.freedns_accounts(id),
|
|
domain VARCHAR(255) NOT NULL,
|
|
freedns_id VARCHAR(100),
|
|
domain_type VARCHAR(50) DEFAULT 'subdomain',
|
|
base_domain VARCHAR(255),
|
|
status VARCHAR(50) DEFAULT 'active',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
|
|
// FreeDNS Records
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.freedns_records (
|
|
id SERIAL PRIMARY KEY,
|
|
domain_id INTEGER REFERENCES admin.freedns_domains(id),
|
|
record_type VARCHAR(10) NOT NULL,
|
|
name VARCHAR(255),
|
|
value TEXT,
|
|
ttl INTEGER DEFAULT 3600,
|
|
freedns_id VARCHAR(100),
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
|
|
// Cloudflare Accounts
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.cloudflare_accounts (
|
|
id SERIAL PRIMARY KEY,
|
|
email VARCHAR(255) NOT NULL,
|
|
api_key VARCHAR(255),
|
|
api_token VARCHAR(255),
|
|
account_id VARCHAR(100),
|
|
status VARCHAR(50) DEFAULT 'active',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
|
|
// Cloudflare Zones
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.cloudflare_zones (
|
|
id SERIAL PRIMARY KEY,
|
|
account_id INTEGER REFERENCES admin.cloudflare_accounts(id),
|
|
zone_id VARCHAR(100) NOT NULL,
|
|
domain VARCHAR(255) NOT NULL,
|
|
status VARCHAR(50),
|
|
name_servers TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
|
|
// Cloudflare DNS Records
|
|
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.cloudflare_records (
|
|
id SERIAL PRIMARY KEY,
|
|
zone_id INTEGER REFERENCES admin.cloudflare_zones(id),
|
|
record_id VARCHAR(100),
|
|
record_type VARCHAR(10),
|
|
name VARCHAR(255),
|
|
content TEXT,
|
|
ttl INTEGER DEFAULT 1,
|
|
proxied BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)");
|
|
}
|
|
|
|
// FreeDNS API Class
|
|
class FreeDNSAPI {
|
|
private $account;
|
|
private $cookie;
|
|
private $baseUrl = 'https://freedns.afraid.org';
|
|
|
|
public function __construct($accountId = null) {
|
|
if ($accountId) {
|
|
$pdo = getDB();
|
|
$stmt = $pdo->prepare("SELECT * FROM admin.freedns_accounts WHERE id = ?");
|
|
$stmt->execute([$accountId]);
|
|
$this->account = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
$this->cookie = $this->account['cookie'] ?? '';
|
|
}
|
|
}
|
|
|
|
public function login($username, $password) {
|
|
$ch = curl_init($this->baseUrl . '/zc.php?action=auth');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => http_build_query([
|
|
'username' => $username,
|
|
'password' => $password,
|
|
'submit' => 'Login',
|
|
'action' => 'auth'
|
|
]),
|
|
CURLOPT_HEADER => true,
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
// Extract cookie
|
|
preg_match_all('/Set-Cookie:\s*([^;]+)/', $response, $matches);
|
|
$cookies = implode('; ', $matches[1] ?? []);
|
|
|
|
if (strpos($response, 'dns_cookie') !== false || strpos($cookies, 'dns_cookie') !== false) {
|
|
$this->cookie = $cookies;
|
|
return ['success' => true, 'cookie' => $cookies];
|
|
}
|
|
|
|
return ['success' => false, 'error' => 'Login failed'];
|
|
}
|
|
|
|
public function getDomains() {
|
|
$ch = curl_init($this->baseUrl . '/subdomain/');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_COOKIE => $this->cookie,
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0'
|
|
]);
|
|
|
|
$html = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
$domains = [];
|
|
// Parse domains from HTML
|
|
preg_match_all('/edit\.php\?data_id=(\d+).*?<td[^>]*>([^<]+)<\/td>/s', $html, $matches, PREG_SET_ORDER);
|
|
|
|
foreach ($matches as $m) {
|
|
$domains[] = [
|
|
'freedns_id' => $m[1],
|
|
'domain' => trim($m[2])
|
|
];
|
|
}
|
|
|
|
return $domains;
|
|
}
|
|
|
|
public function addSubdomain($subdomain, $baseDomain, $type = 'A', $destination) {
|
|
// First get available base domains
|
|
$ch = curl_init($this->baseUrl . '/subdomain/edit.php');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_COOKIE => $this->cookie,
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0'
|
|
]);
|
|
$html = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
// Find domain_id for base domain
|
|
preg_match('/option value="(\d+)"[^>]*>' . preg_quote($baseDomain) . '/i', $html, $m);
|
|
$domainId = $m[1] ?? null;
|
|
|
|
if (!$domainId) {
|
|
return ['success' => false, 'error' => 'Base domain not found'];
|
|
}
|
|
|
|
// Create subdomain
|
|
$ch = curl_init($this->baseUrl . '/subdomain/save.php?step=2');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_COOKIE => $this->cookie,
|
|
CURLOPT_POSTFIELDS => http_build_query([
|
|
'type' => $type,
|
|
'subdomain' => $subdomain,
|
|
'domain_id' => $domainId,
|
|
'address' => $destination,
|
|
'send' => 'Save!'
|
|
]),
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0',
|
|
CURLOPT_FOLLOWLOCATION => true
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if (strpos($response, 'ERROR') !== false) {
|
|
preg_match('/ERROR[^<]*/', $response, $err);
|
|
return ['success' => false, 'error' => $err[0] ?? 'Unknown error'];
|
|
}
|
|
|
|
return ['success' => true, 'domain' => "$subdomain.$baseDomain"];
|
|
}
|
|
|
|
public function deleteSubdomain($freednsId) {
|
|
$ch = curl_init($this->baseUrl . "/subdomain/delete2.php?data_id[]=$freednsId&submit=delete");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_COOKIE => $this->cookie,
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0',
|
|
CURLOPT_FOLLOWLOCATION => true
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
return ['success' => true];
|
|
}
|
|
|
|
public function getAvailableBaseDomains() {
|
|
$ch = curl_init($this->baseUrl . '/domain/registry/');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_COOKIE => $this->cookie,
|
|
CURLOPT_USERAGENT => 'Mozilla/5.0'
|
|
]);
|
|
|
|
$html = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
$domains = [];
|
|
preg_match_all('/<td[^>]*>([a-z0-9.-]+\.[a-z]{2,})<\/td>/i', $html, $matches);
|
|
|
|
return array_unique($matches[1] ?? []);
|
|
}
|
|
}
|
|
|
|
// Cloudflare API Class
|
|
class CloudflareAPI {
|
|
private $email;
|
|
private $apiKey;
|
|
private $apiToken;
|
|
private $baseUrl = 'https://api.cloudflare.com/client/v4';
|
|
|
|
public function __construct($accountId = null) {
|
|
if ($accountId) {
|
|
$pdo = getDB();
|
|
$stmt = $pdo->prepare("SELECT * FROM admin.cloudflare_accounts WHERE id = ?");
|
|
$stmt->execute([$accountId]);
|
|
$account = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
$this->email = $account['email'];
|
|
$this->apiKey = $account['api_key'];
|
|
$this->apiToken = $account['api_token'];
|
|
}
|
|
}
|
|
|
|
public function setCredentials($email, $apiKey, $apiToken = null) {
|
|
$this->email = $email;
|
|
$this->apiKey = $apiKey;
|
|
$this->apiToken = $apiToken;
|
|
}
|
|
|
|
private function request($endpoint, $method = 'GET', $data = null) {
|
|
$ch = curl_init($this->baseUrl . $endpoint);
|
|
|
|
$headers = ['Content-Type: application/json'];
|
|
if ($this->apiToken) {
|
|
$headers[] = 'Authorization: Bearer ' . $this->apiToken;
|
|
} else {
|
|
$headers[] = 'X-Auth-Email: ' . $this->email;
|
|
$headers[] = 'X-Auth-Key: ' . $this->apiKey;
|
|
}
|
|
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HTTPHEADER => $headers,
|
|
CURLOPT_CUSTOMREQUEST => $method
|
|
]);
|
|
|
|
if ($data && in_array($method, ['POST', 'PUT', 'PATCH'])) {
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
|
}
|
|
|
|
$response = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
return json_decode($response, true);
|
|
}
|
|
|
|
public function verifyToken() {
|
|
return $this->request('/user/tokens/verify');
|
|
}
|
|
|
|
public function listZones() {
|
|
return $this->request('/zones?per_page=50');
|
|
}
|
|
|
|
public function getZone($zoneId) {
|
|
return $this->request("/zones/$zoneId");
|
|
}
|
|
|
|
public function createZone($domain, $accountId = null) {
|
|
$data = ['name' => $domain, 'jump_start' => true];
|
|
if ($accountId) $data['account'] = ['id' => $accountId];
|
|
return $this->request('/zones', 'POST', $data);
|
|
}
|
|
|
|
public function deleteZone($zoneId) {
|
|
return $this->request("/zones/$zoneId", 'DELETE');
|
|
}
|
|
|
|
public function listDNSRecords($zoneId) {
|
|
return $this->request("/zones/$zoneId/dns_records?per_page=100");
|
|
}
|
|
|
|
public function createDNSRecord($zoneId, $type, $name, $content, $ttl = 1, $proxied = false) {
|
|
return $this->request("/zones/$zoneId/dns_records", 'POST', [
|
|
'type' => $type,
|
|
'name' => $name,
|
|
'content' => $content,
|
|
'ttl' => $ttl,
|
|
'proxied' => $proxied
|
|
]);
|
|
}
|
|
|
|
public function updateDNSRecord($zoneId, $recordId, $type, $name, $content, $ttl = 1, $proxied = false) {
|
|
return $this->request("/zones/$zoneId/dns_records/$recordId", 'PUT', [
|
|
'type' => $type,
|
|
'name' => $name,
|
|
'content' => $content,
|
|
'ttl' => $ttl,
|
|
'proxied' => $proxied
|
|
]);
|
|
}
|
|
|
|
public function deleteDNSRecord($zoneId, $recordId) {
|
|
return $this->request("/zones/$zoneId/dns_records/$recordId", 'DELETE');
|
|
}
|
|
|
|
// Bulk operations
|
|
public function bulkCreateRecords($zoneId, $records) {
|
|
$results = [];
|
|
foreach ($records as $record) {
|
|
$results[] = $this->createDNSRecord(
|
|
$zoneId,
|
|
$record['type'],
|
|
$record['name'],
|
|
$record['content'],
|
|
$record['ttl'] ?? 1,
|
|
$record['proxied'] ?? false
|
|
);
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
// Email routing setup
|
|
public function setupEmailRouting($zoneId, $mxRecords, $spfRecord, $dkimRecord = null, $dmarcRecord = null) {
|
|
$results = [];
|
|
|
|
// MX Records
|
|
foreach ($mxRecords as $priority => $server) {
|
|
$results[] = $this->createDNSRecord($zoneId, 'MX', '@', $server, 1, false);
|
|
}
|
|
|
|
// SPF
|
|
if ($spfRecord) {
|
|
$results[] = $this->createDNSRecord($zoneId, 'TXT', '@', $spfRecord, 1, false);
|
|
}
|
|
|
|
// DKIM
|
|
if ($dkimRecord) {
|
|
$results[] = $this->createDNSRecord($zoneId, 'TXT', 'default._domainkey', $dkimRecord, 1, false);
|
|
}
|
|
|
|
// DMARC
|
|
if ($dmarcRecord) {
|
|
$results[] = $this->createDNSRecord($zoneId, 'TXT', '_dmarc', $dmarcRecord, 1, false);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
}
|
|
|
|
// API Handler
|
|
if (isset($_GET['action']) || isset($_POST['action'])) {
|
|
header('Content-Type: application/json');
|
|
ensureTables();
|
|
$action = $_GET['action'] ?? $_POST['action'];
|
|
$pdo = getDB();
|
|
|
|
try {
|
|
switch ($action) {
|
|
// Stats
|
|
case 'stats':
|
|
echo json_encode(['success' => true, 'stats' => [
|
|
'freedns_accounts' => $pdo->query("SELECT COUNT(*) FROM admin.freedns_accounts")->fetchColumn(),
|
|
'freedns_domains' => $pdo->query("SELECT COUNT(*) FROM admin.freedns_domains")->fetchColumn(),
|
|
'cloudflare_accounts' => $pdo->query("SELECT COUNT(*) FROM admin.cloudflare_accounts")->fetchColumn(),
|
|
'cloudflare_zones' => $pdo->query("SELECT COUNT(*) FROM admin.cloudflare_zones")->fetchColumn(),
|
|
]]);
|
|
break;
|
|
|
|
// ============ FreeDNS ============
|
|
case 'freedns_add_account':
|
|
$username = $_POST['username'] ?? '';
|
|
$password = $_POST['password'] ?? '';
|
|
|
|
$api = new FreeDNSAPI();
|
|
$login = $api->login($username, $password);
|
|
|
|
if ($login['success']) {
|
|
$stmt = $pdo->prepare("INSERT INTO admin.freedns_accounts (username, password, cookie, last_login) VALUES (?, ?, ?, NOW()) RETURNING id");
|
|
$stmt->execute([$username, $password, $login['cookie']]);
|
|
$id = $stmt->fetchColumn();
|
|
echo json_encode(['success' => true, 'id' => $id]);
|
|
} else {
|
|
echo json_encode($login);
|
|
}
|
|
break;
|
|
|
|
case 'freedns_list_accounts':
|
|
$accounts = $pdo->query("SELECT id, username, domains_count, max_domains, status, last_login, created_at FROM admin.freedns_accounts ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
|
|
echo json_encode(['success' => true, 'accounts' => $accounts]);
|
|
break;
|
|
|
|
case 'freedns_refresh_account':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$api = new FreeDNSAPI($accountId);
|
|
|
|
// Re-login
|
|
$stmt = $pdo->prepare("SELECT username, password FROM admin.freedns_accounts WHERE id = ?");
|
|
$stmt->execute([$accountId]);
|
|
$acc = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$login = $api->login($acc['username'], $acc['password']);
|
|
if ($login['success']) {
|
|
$pdo->exec("UPDATE admin.freedns_accounts SET cookie = " . $pdo->quote($login['cookie']) . ", last_login = NOW() WHERE id = $accountId");
|
|
|
|
// Get domains
|
|
$api = new FreeDNSAPI($accountId);
|
|
$domains = $api->getDomains();
|
|
|
|
$pdo->exec("UPDATE admin.freedns_accounts SET domains_count = " . count($domains) . " WHERE id = $accountId");
|
|
|
|
echo json_encode(['success' => true, 'domains_count' => count($domains)]);
|
|
} else {
|
|
echo json_encode($login);
|
|
}
|
|
break;
|
|
|
|
case 'freedns_get_domains':
|
|
$accountId = $_GET['account_id'] ?? 0;
|
|
$api = new FreeDNSAPI($accountId);
|
|
$domains = $api->getDomains();
|
|
echo json_encode(['success' => true, 'domains' => $domains]);
|
|
break;
|
|
|
|
case 'freedns_add_subdomain':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$subdomain = $_POST['subdomain'] ?? '';
|
|
$baseDomain = $_POST['base_domain'] ?? '';
|
|
$type = $_POST['type'] ?? 'A';
|
|
$destination = $_POST['destination'] ?? '';
|
|
|
|
// Find account with space
|
|
if (!$accountId) {
|
|
$stmt = $pdo->query("SELECT id FROM admin.freedns_accounts WHERE domains_count < max_domains AND status = 'active' ORDER BY domains_count ASC LIMIT 1");
|
|
$accountId = $stmt->fetchColumn();
|
|
}
|
|
|
|
if (!$accountId) {
|
|
echo json_encode(['success' => false, 'error' => 'No available account (all at max domains)']);
|
|
break;
|
|
}
|
|
|
|
$api = new FreeDNSAPI($accountId);
|
|
$result = $api->addSubdomain($subdomain, $baseDomain, $type, $destination);
|
|
|
|
if ($result['success']) {
|
|
$stmt = $pdo->prepare("INSERT INTO admin.freedns_domains (account_id, domain, base_domain, domain_type) VALUES (?, ?, ?, 'subdomain')");
|
|
$stmt->execute([$accountId, $result['domain'], $baseDomain]);
|
|
$pdo->exec("UPDATE admin.freedns_accounts SET domains_count = domains_count + 1 WHERE id = $accountId");
|
|
}
|
|
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'freedns_delete_subdomain':
|
|
$domainId = $_POST['domain_id'] ?? 0;
|
|
$freednsId = $_POST['freedns_id'] ?? '';
|
|
|
|
$stmt = $pdo->prepare("SELECT account_id FROM admin.freedns_domains WHERE id = ?");
|
|
$stmt->execute([$domainId]);
|
|
$accountId = $stmt->fetchColumn();
|
|
|
|
$api = new FreeDNSAPI($accountId);
|
|
$result = $api->deleteSubdomain($freednsId);
|
|
|
|
if ($result['success']) {
|
|
$pdo->exec("DELETE FROM admin.freedns_domains WHERE id = $domainId");
|
|
$pdo->exec("UPDATE admin.freedns_accounts SET domains_count = domains_count - 1 WHERE id = $accountId");
|
|
}
|
|
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'freedns_base_domains':
|
|
$accountId = $_GET['account_id'] ?? 0;
|
|
$api = new FreeDNSAPI($accountId);
|
|
$domains = $api->getAvailableBaseDomains();
|
|
echo json_encode(['success' => true, 'domains' => $domains]);
|
|
break;
|
|
|
|
case 'freedns_bulk_create':
|
|
$subdomains = json_decode($_POST['subdomains'] ?? '[]', true);
|
|
$baseDomain = $_POST['base_domain'] ?? '';
|
|
$type = $_POST['type'] ?? 'A';
|
|
$destination = $_POST['destination'] ?? '';
|
|
|
|
$results = ['success' => 0, 'failed' => 0, 'errors' => []];
|
|
|
|
foreach ($subdomains as $sub) {
|
|
// Find account with space
|
|
$stmt = $pdo->query("SELECT id FROM admin.freedns_accounts WHERE domains_count < max_domains AND status = 'active' ORDER BY domains_count ASC LIMIT 1");
|
|
$accountId = $stmt->fetchColumn();
|
|
|
|
if (!$accountId) {
|
|
$results['failed']++;
|
|
$results['errors'][] = "No available account for $sub";
|
|
continue;
|
|
}
|
|
|
|
$api = new FreeDNSAPI($accountId);
|
|
$result = $api->addSubdomain($sub, $baseDomain, $type, $destination);
|
|
|
|
if ($result['success']) {
|
|
$stmt = $pdo->prepare("INSERT INTO admin.freedns_domains (account_id, domain, base_domain, domain_type) VALUES (?, ?, ?, 'subdomain')");
|
|
$stmt->execute([$accountId, $result['domain'], $baseDomain]);
|
|
$pdo->exec("UPDATE admin.freedns_accounts SET domains_count = domains_count + 1 WHERE id = $accountId");
|
|
$results['success']++;
|
|
} else {
|
|
$results['failed']++;
|
|
$results['errors'][] = $result['error'] ?? 'Unknown error';
|
|
}
|
|
|
|
usleep(500000); // 0.5s delay
|
|
}
|
|
|
|
echo json_encode(['success' => true, 'results' => $results]);
|
|
break;
|
|
|
|
// ============ Cloudflare ============
|
|
case 'cf_add_account':
|
|
$email = $_POST['email'] ?? '';
|
|
$apiKey = $_POST['api_key'] ?? '';
|
|
$apiToken = $_POST['api_token'] ?? '';
|
|
|
|
$api = new CloudflareAPI();
|
|
$api->setCredentials($email, $apiKey, $apiToken);
|
|
|
|
$verify = $api->verifyToken();
|
|
|
|
if ($verify['success'] ?? false) {
|
|
$stmt = $pdo->prepare("INSERT INTO admin.cloudflare_accounts (email, api_key, api_token) VALUES (?, ?, ?) RETURNING id");
|
|
$stmt->execute([$email, $apiKey, $apiToken]);
|
|
$id = $stmt->fetchColumn();
|
|
echo json_encode(['success' => true, 'id' => $id]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'error' => $verify['errors'][0]['message'] ?? 'Invalid credentials']);
|
|
}
|
|
break;
|
|
|
|
case 'cf_list_accounts':
|
|
$accounts = $pdo->query("SELECT id, email, status, created_at FROM admin.cloudflare_accounts ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
|
|
echo json_encode(['success' => true, 'accounts' => $accounts]);
|
|
break;
|
|
|
|
case 'cf_list_zones':
|
|
$accountId = $_GET['account_id'] ?? 0;
|
|
$api = new CloudflareAPI($accountId);
|
|
$result = $api->listZones();
|
|
|
|
if ($result['success'] ?? false) {
|
|
// Sync to DB
|
|
foreach ($result['result'] as $zone) {
|
|
$stmt = $pdo->prepare("INSERT INTO admin.cloudflare_zones (account_id, zone_id, domain, status, name_servers)
|
|
VALUES (?, ?, ?, ?, ?) ON CONFLICT (zone_id) DO UPDATE SET status = ?, name_servers = ?");
|
|
$ns = implode(',', $zone['name_servers'] ?? []);
|
|
$stmt->execute([$accountId, $zone['id'], $zone['name'], $zone['status'], $ns, $zone['status'], $ns]);
|
|
}
|
|
}
|
|
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'cf_add_zone':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$domain = $_POST['domain'] ?? '';
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
$result = $api->createZone($domain);
|
|
|
|
if ($result['success'] ?? false) {
|
|
$zone = $result['result'];
|
|
$stmt = $pdo->prepare("INSERT INTO admin.cloudflare_zones (account_id, zone_id, domain, status, name_servers) VALUES (?, ?, ?, ?, ?)");
|
|
$stmt->execute([$accountId, $zone['id'], $zone['name'], $zone['status'], implode(',', $zone['name_servers'] ?? [])]);
|
|
}
|
|
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'cf_delete_zone':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$zoneId = $_POST['zone_id'] ?? '';
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
$result = $api->deleteZone($zoneId);
|
|
|
|
if ($result['success'] ?? false) {
|
|
$pdo->exec("DELETE FROM admin.cloudflare_zones WHERE zone_id = " . $pdo->quote($zoneId));
|
|
}
|
|
|
|
echo json_encode($result);
|
|
break;
|
|
|
|
case 'cf_list_records':
|
|
$accountId = $_GET['account_id'] ?? 0;
|
|
$zoneId = $_GET['zone_id'] ?? '';
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
echo json_encode($api->listDNSRecords($zoneId));
|
|
break;
|
|
|
|
case 'cf_add_record':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$zoneId = $_POST['zone_id'] ?? '';
|
|
$type = $_POST['type'] ?? 'A';
|
|
$name = $_POST['name'] ?? '@';
|
|
$content = $_POST['content'] ?? '';
|
|
$ttl = intval($_POST['ttl'] ?? 1);
|
|
$proxied = ($_POST['proxied'] ?? '0') === '1';
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
echo json_encode($api->createDNSRecord($zoneId, $type, $name, $content, $ttl, $proxied));
|
|
break;
|
|
|
|
case 'cf_delete_record':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$zoneId = $_POST['zone_id'] ?? '';
|
|
$recordId = $_POST['record_id'] ?? '';
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
echo json_encode($api->deleteDNSRecord($zoneId, $recordId));
|
|
break;
|
|
|
|
case 'cf_setup_email':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$zoneId = $_POST['zone_id'] ?? '';
|
|
$ip = $_POST['ip'] ?? '';
|
|
$domain = $_POST['domain'] ?? '';
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
|
|
// Standard email records
|
|
$results = [];
|
|
|
|
// A record
|
|
$results[] = $api->createDNSRecord($zoneId, 'A', '@', $ip, 1, false);
|
|
$results[] = $api->createDNSRecord($zoneId, 'A', 'mail', $ip, 1, false);
|
|
|
|
// MX
|
|
$results[] = $api->createDNSRecord($zoneId, 'MX', '@', "mail.$domain", 1, false);
|
|
|
|
// SPF
|
|
$results[] = $api->createDNSRecord($zoneId, 'TXT', '@', "v=spf1 ip4:$ip ~all", 1, false);
|
|
|
|
// DMARC
|
|
$results[] = $api->createDNSRecord($zoneId, 'TXT', '_dmarc', 'v=DMARC1; p=none; rua=mailto:dmarc@'.$domain, 1, false);
|
|
|
|
echo json_encode(['success' => true, 'results' => $results]);
|
|
break;
|
|
|
|
case 'cf_bulk_add_records':
|
|
$accountId = $_POST['account_id'] ?? 0;
|
|
$zoneId = $_POST['zone_id'] ?? '';
|
|
$records = json_decode($_POST['records'] ?? '[]', true);
|
|
|
|
$api = new CloudflareAPI($accountId);
|
|
$results = $api->bulkCreateRecords($zoneId, $records);
|
|
|
|
echo json_encode(['success' => true, 'results' => $results]);
|
|
break;
|
|
|
|
default:
|
|
echo json_encode(['success' => false, 'error' => 'Unknown action']);
|
|
}
|
|
} catch (Exception $e) {
|
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
ensureTables();
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>DNS Manager - FreeDNS & Cloudflare</title>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<style>
|
|
:root{--bg:#0a0a0f;--bg2:#12121a;--bg3:#1a1a25;--primary:#6366f1;--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--text:#e2e8f0;--text2:#94a3b8;--border:#2a2a3a;--freedns:#22c55e;--cloudflare:#f38020}
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
|
.header{background:linear-gradient(135deg,var(--bg2),var(--bg3));border-bottom:1px solid var(--border);padding:1rem 2rem;display:flex;justify-content:space-between;align-items:center}
|
|
.header h1{font-size:1.3rem;display:flex;align-items:center;gap:.5rem}
|
|
.tabs{display:flex;gap:0;background:var(--bg3);border-radius:8px;padding:4px}
|
|
.tab{padding:.6rem 1.5rem;cursor:pointer;border-radius:6px;font-weight:600;font-size:.85rem;transition:all .2s;display:flex;align-items:center;gap:.5rem}
|
|
.tab:hover{background:rgba(255,255,255,.05)}
|
|
.tab.active{background:var(--primary);color:white}
|
|
.tab.freedns.active{background:var(--freedns)}
|
|
.tab.cloudflare.active{background:var(--cloudflare)}
|
|
.container{max-width:1400px;margin:0 auto;padding:1.5rem}
|
|
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;margin-bottom:1.5rem}
|
|
.stat{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:1rem;text-align:center}
|
|
.stat .v{font-size:1.8rem;font-weight:700}.stat .l{color:var(--text2);font-size:.75rem}
|
|
.stat.freedns .v{color:var(--freedns)}.stat.cloudflare .v{color:var(--cloudflare)}
|
|
.panel{display:none}.panel.active{display:block}
|
|
.card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:1.25rem;margin-bottom:1rem}
|
|
.card h3{margin-bottom:1rem;font-size:1rem;display:flex;align-items:center;gap:.5rem}
|
|
.form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;margin-bottom:1rem}
|
|
.form-group{display:flex;flex-direction:column;gap:.3rem}
|
|
.form-group label{font-size:.75rem;color:var(--text2)}
|
|
.form-group input,.form-group select{padding:.6rem .8rem;background:var(--bg3);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.85rem}
|
|
.form-group input:focus,.form-group select:focus{outline:none;border-color:var(--primary)}
|
|
.btn{padding:.5rem 1rem;border:none;border-radius:6px;font-weight:600;cursor:pointer;transition:all .2s;display:inline-flex;align-items:center;gap:.4rem;font-size:.8rem}
|
|
.btn:hover{transform:translateY(-1px);opacity:.9}
|
|
.btn-primary{background:var(--primary);color:white}
|
|
.btn-success{background:var(--success);color:white}
|
|
.btn-danger{background:var(--danger);color:white}
|
|
.btn-freedns{background:var(--freedns);color:white}
|
|
.btn-cloudflare{background:var(--cloudflare);color:white}
|
|
.btn-sm{padding:.35rem .7rem;font-size:.75rem}
|
|
table{width:100%;border-collapse:collapse;font-size:.8rem}
|
|
th,td{padding:.6rem;text-align:left;border-bottom:1px solid var(--border)}
|
|
th{color:var(--text2);font-weight:500;font-size:.7rem;text-transform:uppercase}
|
|
tr:hover{background:rgba(255,255,255,.02)}
|
|
.badge{padding:.2rem .5rem;border-radius:10px;font-size:.65rem;font-weight:600}
|
|
.badge-active{background:rgba(16,185,129,.15);color:var(--success)}
|
|
.badge-pending{background:rgba(245,158,11,.15);color:var(--warning)}
|
|
.badge-full{background:rgba(239,68,68,.15);color:var(--danger)}
|
|
.progress-mini{width:60px;height:6px;background:var(--bg3);border-radius:3px;overflow:hidden}
|
|
.progress-mini .fill{height:100%;background:var(--success);border-radius:3px}
|
|
.empty{text-align:center;padding:2rem;color:var(--text2)}
|
|
.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:100;align-items:center;justify-content:center}
|
|
.modal.active{display:flex}
|
|
.modal-content{background:var(--bg2);border-radius:12px;padding:1.5rem;width:500px;max-width:90%;max-height:80vh;overflow-y:auto}
|
|
.modal-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}
|
|
.modal-close{background:none;border:none;color:var(--text2);font-size:1.2rem;cursor:pointer}
|
|
textarea{width:100%;padding:.6rem;background:var(--bg3);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.8rem;min-height:100px;resize:vertical}
|
|
.zone-card{background:var(--bg3);border-radius:8px;padding:1rem;margin-bottom:.75rem;cursor:pointer;transition:all .2s}
|
|
.zone-card:hover{background:rgba(99,102,241,.1);border-color:var(--primary)}
|
|
.zone-card h4{margin-bottom:.25rem}.zone-card p{font-size:.75rem;color:var(--text2)}
|
|
</style>
|
|
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1><i class="fas fa-globe"></i> DNS Manager</h1>
|
|
<div class="tabs">
|
|
<div class="tab freedns active" data-tab="freedns"><i class="fas fa-leaf"></i> FreeDNS</div>
|
|
<div class="tab cloudflare" data-tab="cloudflare"><i class="fab fa-cloudflare"></i> Cloudflare</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="stats">
|
|
<div class="stat freedns"><div class="v" id="sFdnsAcc">0</div><div class="l">FreeDNS Accounts</div></div>
|
|
<div class="stat freedns"><div class="v" id="sFdnsDom">0</div><div class="l">FreeDNS Domains</div></div>
|
|
<div class="stat cloudflare"><div class="v" id="sCfAcc">0</div><div class="l">Cloudflare Accounts</div></div>
|
|
<div class="stat cloudflare"><div class="v" id="sCfZones">0</div><div class="l">Cloudflare Zones</div></div>
|
|
</div>
|
|
|
|
<!-- FreeDNS Panel -->
|
|
<div class="panel active" id="panel-freedns">
|
|
<div class="card">
|
|
<h3><i class="fas fa-plus" style="color:var(--freedns)"></i> Add FreeDNS Account</h3>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Username</label><input type="text" id="fdnsUser" placeholder="username"></div>
|
|
<div class="form-group"><label>Password</label><input type="password" id="fdnsPass" placeholder="password"></div>
|
|
<div class="form-group"><label> </label><button class="btn btn-freedns" onclick="addFreeDNSAccount()"><i class="fas fa-plus"></i> Add Account</button></div>
|
|
</div>
|
|
<p style="font-size:.75rem;color:var(--text2)"><i class="fas fa-info-circle"></i> FreeDNS limite à 5 domaines par compte. Créez plusieurs comptes pour plus de domaines.</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-users"></i> FreeDNS Accounts</h3>
|
|
<table>
|
|
<thead><tr><th>ID</th><th>Username</th><th>Domains</th><th>Capacity</th><th>Status</th><th>Last Login</th><th>Actions</th></tr></thead>
|
|
<tbody id="fdnsAccounts"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-plus-circle" style="color:var(--freedns)"></i> Create Subdomain</h3>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Subdomain</label><input type="text" id="fdnsSub" placeholder="mysite"></div>
|
|
<div class="form-group"><label>Base Domain</label><select id="fdnsBase"><option>Loading...</option></select></div>
|
|
<div class="form-group"><label>Type</label><select id="fdnsType"><option value="A">A</option><option value="AAAA">AAAA</option><option value="CNAME">CNAME</option><option value="MX">MX</option><option value="TXT">TXT</option></select></div>
|
|
<div class="form-group"><label>Destination (IP/Host)</label><input type="text" id="fdnsDest" placeholder="1.2.3.4"></div>
|
|
</div>
|
|
<button class="btn btn-freedns" onclick="addFreeDNSSubdomain()"><i class="fas fa-plus"></i> Create Subdomain</button>
|
|
<button class="btn btn-primary" onclick="showBulkFreeDNS()"><i class="fas fa-layer-group"></i> Bulk Create</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-list"></i> My Subdomains</h3>
|
|
<table>
|
|
<thead><tr><th>Domain</th><th>Base</th><th>Account</th><th>Status</th><th>Actions</th></tr></thead>
|
|
<tbody id="fdnsDomains"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cloudflare Panel -->
|
|
<div class="panel" id="panel-cloudflare">
|
|
<div class="card">
|
|
<h3><i class="fas fa-plus" style="color:var(--cloudflare)"></i> Add Cloudflare Account</h3>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Email</label><input type="email" id="cfEmail" placeholder="email@example.com"></div>
|
|
<div class="form-group"><label>Global API Key</label><input type="password" id="cfApiKey" placeholder="API Key"></div>
|
|
<div class="form-group"><label>API Token (optional)</label><input type="password" id="cfApiToken" placeholder="API Token"></div>
|
|
<div class="form-group"><label> </label><button class="btn btn-cloudflare" onclick="addCloudflareAccount()"><i class="fas fa-plus"></i> Add Account</button></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-users"></i> Cloudflare Accounts</h3>
|
|
<table>
|
|
<thead><tr><th>ID</th><th>Email</th><th>Status</th><th>Created</th><th>Actions</th></tr></thead>
|
|
<tbody id="cfAccounts"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3><i class="fas fa-layer-group"></i> Zones</h3>
|
|
<div id="cfZones" class="empty">Select an account to view zones</div>
|
|
</div>
|
|
|
|
<div class="card" id="cfRecordsCard" style="display:none">
|
|
<h3><i class="fas fa-dns"></i> DNS Records - <span id="cfCurrentZone"></span></h3>
|
|
<div class="form-row" style="margin-bottom:1rem">
|
|
<div class="form-group"><label>Type</label><select id="cfRecType"><option value="A">A</option><option value="AAAA">AAAA</option><option value="CNAME">CNAME</option><option value="MX">MX</option><option value="TXT">TXT</option><option value="NS">NS</option></select></div>
|
|
<div class="form-group"><label>Name</label><input type="text" id="cfRecName" placeholder="@ or subdomain"></div>
|
|
<div class="form-group"><label>Content</label><input type="text" id="cfRecContent" placeholder="IP or value"></div>
|
|
<div class="form-group"><label>Proxied</label><select id="cfRecProxied"><option value="0">No (DNS only)</option><option value="1">Yes (Proxied)</option></select></div>
|
|
<div class="form-group"><label> </label><button class="btn btn-cloudflare" onclick="addCfRecord()"><i class="fas fa-plus"></i> Add</button></div>
|
|
</div>
|
|
<button class="btn btn-success btn-sm" onclick="setupEmailRecords()"><i class="fas fa-envelope"></i> Setup Email Records</button>
|
|
<table style="margin-top:1rem">
|
|
<thead><tr><th>Type</th><th>Name</th><th>Content</th><th>Proxied</th><th>TTL</th><th>Actions</th></tr></thead>
|
|
<tbody id="cfRecords"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk FreeDNS Modal -->
|
|
<div class="modal" id="bulkModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3><i class="fas fa-layer-group"></i> Bulk Create Subdomains</h3>
|
|
<button class="modal-close" onclick="closeBulkModal()">×</button>
|
|
</div>
|
|
<div class="form-group"><label>Subdomains (one per line)</label><textarea id="bulkSubs" placeholder="sub1 sub2 sub3"></textarea></div>
|
|
<div class="form-row">
|
|
<div class="form-group"><label>Base Domain</label><select id="bulkBase"><option>Loading...</option></select></div>
|
|
<div class="form-group"><label>Type</label><select id="bulkType"><option value="A">A</option><option value="CNAME">CNAME</option></select></div>
|
|
</div>
|
|
<div class="form-group"><label>Destination</label><input type="text" id="bulkDest" placeholder="1.2.3.4"></div>
|
|
<button class="btn btn-freedns" onclick="bulkCreateFreeDNS()" style="margin-top:1rem"><i class="fas fa-rocket"></i> Create All</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentCfAccount = null;
|
|
let currentZoneId = null;
|
|
|
|
// Tabs
|
|
document.querySelectorAll('.tab').forEach(t => {
|
|
t.onclick = () => {
|
|
document.querySelectorAll('.tab').forEach(x => x.classList.remove('active'));
|
|
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
|
t.classList.add('active');
|
|
document.getElementById('panel-' + t.dataset.tab).classList.add('active');
|
|
};
|
|
});
|
|
|
|
// Load stats
|
|
function loadStats() {
|
|
fetch('?action=stats').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('sFdnsAcc').textContent = d.stats.freedns_accounts;
|
|
document.getElementById('sFdnsDom').textContent = d.stats.freedns_domains;
|
|
document.getElementById('sCfAcc').textContent = d.stats.cloudflare_accounts;
|
|
document.getElementById('sCfZones').textContent = d.stats.cloudflare_zones;
|
|
}
|
|
});
|
|
}
|
|
|
|
// FreeDNS
|
|
function loadFreeDNSAccounts() {
|
|
fetch('?action=freedns_list_accounts').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('fdnsAccounts').innerHTML = d.accounts.length ? d.accounts.map(a => `
|
|
<tr>
|
|
<td>#${a.id}</td>
|
|
<td>${a.username}</td>
|
|
<td>${a.domains_count}/${a.max_domains}</td>
|
|
<td><div class="progress-mini"><div class="fill" style="width:${(a.domains_count/a.max_domains)*100}%"></div></div></td>
|
|
<td><span class="badge ${a.domains_count >= a.max_domains ? 'badge-full' : 'badge-active'}">${a.domains_count >= a.max_domains ? 'Full' : 'Active'}</span></td>
|
|
<td>${a.last_login ? new Date(a.last_login).toLocaleDateString() : '-'}</td>
|
|
<td>
|
|
<button class="btn btn-primary btn-sm" onclick="refreshFreeDNS(${a.id})"><i class="fas fa-sync"></i></button>
|
|
<button class="btn btn-success btn-sm" onclick="loadFreeDNSDomains(${a.id})"><i class="fas fa-list"></i></button>
|
|
</td>
|
|
</tr>
|
|
`).join('') : '<tr><td colspan="7" class="empty">No accounts yet</td></tr>';
|
|
|
|
// Load base domains from first account
|
|
if (d.accounts.length) loadBaseDomains(d.accounts[0].id);
|
|
}
|
|
});
|
|
}
|
|
|
|
function addFreeDNSAccount() {
|
|
const fd = new FormData();
|
|
fd.append('action', 'freedns_add_account');
|
|
fd.append('username', document.getElementById('fdnsUser').value);
|
|
fd.append('password', document.getElementById('fdnsPass').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert('Account added!');
|
|
loadFreeDNSAccounts();
|
|
loadStats();
|
|
} else {
|
|
alert('Error: ' + d.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshFreeDNS(id) {
|
|
const fd = new FormData();
|
|
fd.append('action', 'freedns_refresh_account');
|
|
fd.append('account_id', id);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert('Refreshed! ' + d.domains_count + ' domains');
|
|
loadFreeDNSAccounts();
|
|
} else {
|
|
alert('Error: ' + d.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadBaseDomains(accountId) {
|
|
fetch('?action=freedns_base_domains&account_id=' + accountId).then(r=>r.json()).then(d => {
|
|
if (d.success && d.domains.length) {
|
|
const opts = d.domains.slice(0, 50).map(dom => `<option value="${dom}">${dom}</option>`).join('');
|
|
document.getElementById('fdnsBase').innerHTML = opts;
|
|
document.getElementById('bulkBase').innerHTML = opts;
|
|
}
|
|
});
|
|
}
|
|
|
|
function addFreeDNSSubdomain() {
|
|
const fd = new FormData();
|
|
fd.append('action', 'freedns_add_subdomain');
|
|
fd.append('subdomain', document.getElementById('fdnsSub').value);
|
|
fd.append('base_domain', document.getElementById('fdnsBase').value);
|
|
fd.append('type', document.getElementById('fdnsType').value);
|
|
fd.append('destination', document.getElementById('fdnsDest').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert('Created: ' + d.domain);
|
|
loadFreeDNSAccounts();
|
|
loadStats();
|
|
} else {
|
|
alert('Error: ' + d.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadFreeDNSDomains(accountId) {
|
|
fetch('?action=freedns_get_domains&account_id=' + accountId).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('fdnsDomains').innerHTML = d.domains.length ? d.domains.map(dom => `
|
|
<tr>
|
|
<td>${dom.domain}</td>
|
|
<td>-</td>
|
|
<td>#${accountId}</td>
|
|
<td><span class="badge badge-active">Active</span></td>
|
|
<td><button class="btn btn-danger btn-sm" onclick="deleteFreeDNS('${dom.freedns_id}')"><i class="fas fa-trash"></i></button></td>
|
|
</tr>
|
|
`).join('') : '<tr><td colspan="5" class="empty">No domains</td></tr>';
|
|
}
|
|
});
|
|
}
|
|
|
|
function showBulkFreeDNS() {
|
|
document.getElementById('bulkModal').classList.add('active');
|
|
}
|
|
|
|
function closeBulkModal() {
|
|
document.getElementById('bulkModal').classList.remove('active');
|
|
}
|
|
|
|
function bulkCreateFreeDNS() {
|
|
const subs = document.getElementById('bulkSubs').value.split('\n').filter(s => s.trim());
|
|
const fd = new FormData();
|
|
fd.append('action', 'freedns_bulk_create');
|
|
fd.append('subdomains', JSON.stringify(subs));
|
|
fd.append('base_domain', document.getElementById('bulkBase').value);
|
|
fd.append('type', document.getElementById('bulkType').value);
|
|
fd.append('destination', document.getElementById('bulkDest').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert(`Done!\nSuccess: ${d.results.success}\nFailed: ${d.results.failed}`);
|
|
closeBulkModal();
|
|
loadFreeDNSAccounts();
|
|
loadStats();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Cloudflare
|
|
function loadCloudflareAccounts() {
|
|
fetch('?action=cf_list_accounts').then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
document.getElementById('cfAccounts').innerHTML = d.accounts.length ? d.accounts.map(a => `
|
|
<tr>
|
|
<td>#${a.id}</td>
|
|
<td>${a.email}</td>
|
|
<td><span class="badge badge-active">${a.status}</span></td>
|
|
<td>${new Date(a.created_at).toLocaleDateString()}</td>
|
|
<td>
|
|
<button class="btn btn-cloudflare btn-sm" onclick="loadCfZones(${a.id})"><i class="fas fa-layer-group"></i> Zones</button>
|
|
</td>
|
|
</tr>
|
|
`).join('') : '<tr><td colspan="5" class="empty">No accounts</td></tr>';
|
|
}
|
|
});
|
|
}
|
|
|
|
function addCloudflareAccount() {
|
|
const fd = new FormData();
|
|
fd.append('action', 'cf_add_account');
|
|
fd.append('email', document.getElementById('cfEmail').value);
|
|
fd.append('api_key', document.getElementById('cfApiKey').value);
|
|
fd.append('api_token', document.getElementById('cfApiToken').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert('Account added!');
|
|
loadCloudflareAccounts();
|
|
loadStats();
|
|
} else {
|
|
alert('Error: ' + d.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadCfZones(accountId) {
|
|
currentCfAccount = accountId;
|
|
fetch('?action=cf_list_zones&account_id=' + accountId).then(r=>r.json()).then(d => {
|
|
if (d.success && d.result) {
|
|
document.getElementById('cfZones').innerHTML = d.result.length ? d.result.map(z => `
|
|
<div class="zone-card" onclick="loadCfRecords('${z.id}', '${z.name}')">
|
|
<h4>${z.name}</h4>
|
|
<p>Status: ${z.status} | NS: ${(z.name_servers || []).join(', ')}</p>
|
|
</div>
|
|
`).join('') : '<div class="empty">No zones</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadCfRecords(zoneId, domain) {
|
|
currentZoneId = zoneId;
|
|
document.getElementById('cfCurrentZone').textContent = domain;
|
|
document.getElementById('cfRecordsCard').style.display = 'block';
|
|
|
|
fetch(`?action=cf_list_records&account_id=${currentCfAccount}&zone_id=${zoneId}`).then(r=>r.json()).then(d => {
|
|
if (d.success && d.result) {
|
|
document.getElementById('cfRecords').innerHTML = d.result.map(r => `
|
|
<tr>
|
|
<td><span class="badge" style="background:var(--bg3)">${r.type}</span></td>
|
|
<td>${r.name}</td>
|
|
<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis">${r.content}</td>
|
|
<td>${r.proxied ? '☁️' : '⚡'}</td>
|
|
<td>${r.ttl === 1 ? 'Auto' : r.ttl}</td>
|
|
<td><button class="btn btn-danger btn-sm" onclick="deleteCfRecord('${r.id}')"><i class="fas fa-trash"></i></button></td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
});
|
|
}
|
|
|
|
function addCfRecord() {
|
|
const fd = new FormData();
|
|
fd.append('action', 'cf_add_record');
|
|
fd.append('account_id', currentCfAccount);
|
|
fd.append('zone_id', currentZoneId);
|
|
fd.append('type', document.getElementById('cfRecType').value);
|
|
fd.append('name', document.getElementById('cfRecName').value);
|
|
fd.append('content', document.getElementById('cfRecContent').value);
|
|
fd.append('proxied', document.getElementById('cfRecProxied').value);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
loadCfRecords(currentZoneId, document.getElementById('cfCurrentZone').textContent);
|
|
} else {
|
|
alert('Error: ' + (d.errors?.[0]?.message || 'Unknown'));
|
|
}
|
|
});
|
|
}
|
|
|
|
function deleteCfRecord(recordId) {
|
|
if (!confirm('Delete this record?')) return;
|
|
const fd = new FormData();
|
|
fd.append('action', 'cf_delete_record');
|
|
fd.append('account_id', currentCfAccount);
|
|
fd.append('zone_id', currentZoneId);
|
|
fd.append('record_id', recordId);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
loadCfRecords(currentZoneId, document.getElementById('cfCurrentZone').textContent);
|
|
});
|
|
}
|
|
|
|
function setupEmailRecords() {
|
|
const ip = prompt('Enter server IP for email records:');
|
|
if (!ip) return;
|
|
const domain = document.getElementById('cfCurrentZone').textContent;
|
|
|
|
const fd = new FormData();
|
|
fd.append('action', 'cf_setup_email');
|
|
fd.append('account_id', currentCfAccount);
|
|
fd.append('zone_id', currentZoneId);
|
|
fd.append('ip', ip);
|
|
fd.append('domain', domain);
|
|
|
|
fetch('', {method:'POST', body:fd}).then(r=>r.json()).then(d => {
|
|
if (d.success) {
|
|
alert('Email records created!');
|
|
loadCfRecords(currentZoneId, domain);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Init
|
|
loadStats();
|
|
loadFreeDNSAccounts();
|
|
loadCloudflareAccounts();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|