Files
wevads-platform/scripts/dns-manager.php
2026-02-26 04:53:11 +01:00

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>&nbsp;</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>&nbsp;</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>&nbsp;</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()">&times;</button>
</div>
<div class="form-group"><label>Subdomains (one per line)</label><textarea id="bulkSubs" placeholder="sub1&#10;sub2&#10;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>