/**
* 🛰️ UNIFIED CF PAGES TOOLKIT (v18 — Delete Project + Pages Manager + CF DNS Sync)
*/
const CF_API = "https://api.cloudflare.com/client/v4";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, X-Auth-Email, X-Auth-Key",
};
const TEMPLATES = {
v1: [
"export default {",
" async fetch(request, env) {",
" let url = new URL(request.url);",
" if (url.pathname.startsWith('/')) {",
" const arrStr = [",
"__BACKENDS__",
" ];",
" url.protocol = 'https:';",
" url.hostname = getRandomArray(arrStr);",
" let new_request = new Request(url, request);",
" return fetch(new_request);",
" }",
" return env.ASSETS.fetch(request);",
" }",
"};",
"function getRandomArray(array) {",
" const randomIndex = Math.floor(Math.random() * array.length);",
" return array[randomIndex];",
"}",
].join("\n"),
v2: [
"export default {",
" async fetch(request, env, ctx) {",
" let url = new URL(request.url);",
" let domainBackend = [",
"__BACKENDS__",
" ];",
" if(env.HOST) domainBackend = await ADD(env.HOST);",
" let pathUji = env.PATH || '/';",
" if (pathUji.charAt(0) !== '/') pathUji = '/' + pathUji;",
" let kodeRespon = env.CODE || '200';",
" async function getValidResponse(request, hosts) {",
" let shuffledHosts = hosts.sort(function() { return Math.random() - 0.5; });",
" for (const host of shuffledHosts) {",
" let testUrl = new URL(url.href);",
" testUrl.hostname = host;",
" testUrl.pathname = pathUji.split('?')[0];",
" testUrl.search = pathUji.split('?')[1] || '';",
" try {",
" const response = await fetch(testUrl.href, { method: 'HEAD', redirect: 'manual' });",
" if (response.status.toString() === kodeRespon || response.status === 302) {",
" let finalUrl = new URL(url.href);",
" finalUrl.hostname = host;",
" const newHeaders = new Headers(request.headers);",
" newHeaders.set('Host', host);",
" return await fetch(new Request(finalUrl.href, { method: request.method, headers: newHeaders, body: request.body, redirect: 'follow' }));",
" }",
" } catch (error) {}",
" }",
" return new Response('Down', { status: 503 });",
" }",
" return await getValidResponse(request, domainBackend);",
" }",
"};",
"async function ADD(envadd) {",
" var addtext = envadd.replace(/[\\t |\"'`\\r\\n]+/g, ',').replace(/,+/g, ',');",
" if (addtext.charAt(0) == ',') addtext = addtext.slice(1);",
" if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, -1);",
" return addtext.split(',');",
"}",
].join("\n"),
v3: [
"const WORKER_URLS = [",
"__BACKENDS__",
"];",
"const UA_LIST = ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36'];",
"async function fetchWithTimeout(resource, options) {",
" options = options || {};",
" const timeout = options.timeout || 8000;",
" const controller = new AbortController();",
" const id = setTimeout(function() { controller.abort(); }, timeout);",
" try { return await fetch(resource, Object.assign({}, options, { signal: controller.signal })); }",
" finally { clearTimeout(id); }",
"}",
"export default {",
" async fetch(request, env, ctx) {",
" const url = new URL(request.url);",
" const shuffled = WORKER_URLS.slice().sort(function() { return Math.random() - 0.5; });",
" for (const host of shuffled) {",
" try {",
" const targetUrl = new URL(url.href);",
" targetUrl.hostname = host;",
" targetUrl.protocol = 'https:';",
" const headers = new Headers(request.headers);",
" headers.set('Host', host);",
" headers.set('User-Agent', UA_LIST[0]);",
" const newRequest = new Request(targetUrl.href, { method: request.method, headers: headers, body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null, redirect: 'manual' });",
" const response = await fetchWithTimeout(newRequest);",
" if (response.status !== 429 && response.status < 500) return response;",
" } catch (e) { continue; }",
" }",
" return new Response('All backends down', { status: 503 });",
" }",
"};",
].join("\n"),
v4: [
"const WORKER_URLS = [",
"__BACKENDS__",
"];",
"export default {",
" async fetch(request, env, ctx) {",
" const shuffled = WORKER_URLS.slice().sort(function() { return Math.random() - 0.5; });",
" for (const host of shuffled) {",
" try {",
" const targetUrl = new URL(request.url);",
" targetUrl.hostname = host;",
" targetUrl.protocol = 'https:';",
" const response = await fetch(new Request(targetUrl.toString(), { method: request.method, headers: request.headers, body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null, redirect: 'manual' }));",
" if (response.status !== 429 && response.status < 500) return response;",
" } catch (e) { continue; }",
" }",
" return new Response('All workers down', { status: 503 });",
" }",
"};",
].join("\n"),
v5: [
"const CONFIG = {",
" TARGETS: [",
"__BACKENDS__",
" ],",
" TIMEOUT_MS: 5000,",
" ENABLE_CORS: true",
"};",
"export default {",
" async fetch(request, env, ctx) {",
" if (CONFIG.ENABLE_CORS && request.method === 'OPTIONS') {",
" return new Response(null, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': '*', 'Access-Control-Max-Age': '86400' } });",
" }",
" const shuffledTargets = CONFIG.TARGETS.slice().sort(function() { return Math.random() - 0.5; });",
" const clientIP = request.headers.get('CF-Connecting-IP');",
" for (const host of shuffledTargets) {",
" try {",
" const url = new URL(request.url);",
" url.hostname = host;",
" url.protocol = 'https:';",
" const headers = new Headers(request.headers);",
" headers.set('Host', host);",
" if (clientIP) headers.set('X-Forwarded-For', clientIP);",
" const controller = new AbortController();",
" const timeoutId = setTimeout(function() { controller.abort(); }, CONFIG.TIMEOUT_MS);",
" const response = await fetch(new Request(url.toString(), { method: request.method, headers: headers, body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null, redirect: 'manual', signal: controller.signal }));",
" clearTimeout(timeoutId);",
" if (response.status < 500 && response.status !== 429) {",
" if (CONFIG.ENABLE_CORS) {",
" const nh = new Headers(response.headers);",
" nh.set('Access-Control-Allow-Origin', '*');",
" return new Response(response.body, { status: response.status, headers: nh });",
" }",
" return response;",
" }",
" } catch (err) { continue; }",
" }",
" return new Response(JSON.stringify({ status: 503, error: 'All servers down' }), { status: 503, headers: { 'Content-Type': 'application/json' } });",
" }",
"};",
].join("\n"),
};
const DEFAULT_TEMPLATE_LINES = [
"export default {",
" async fetch(request, env, ctx) {",
" let url = new URL(request.url);",
" let domainBackend = ['payload.goku25.workers.dev'];",
" if(env.HOST) domainBackend = await ADD(env.HOST);",
" let pathUji = env.PATH || '/';",
" if (pathUji.charAt(0) !== '/') pathUji = '/' + pathUji;",
" let kodeRespon = env.CODE || '200';",
" async function getValidResponse(request, hosts) {",
" let shuffledHosts = hosts.sort(function() { return Math.random() - 0.5; });",
" for (const host of shuffledHosts) {",
" let testUrl = new URL(url.href);",
" testUrl.hostname = host;",
" testUrl.pathname = pathUji.split('?')[0];",
" testUrl.search = pathUji.split('?')[1] || '';",
" try {",
" const response = await fetch(testUrl.href, { method: 'HEAD', redirect: 'manual' });",
" if (response.status.toString() === kodeRespon || response.status === 302) {",
" let finalUrl = new URL(url.href);",
" finalUrl.hostname = host;",
" return await fetch(new Request(finalUrl.href, { method: request.method, headers: new Headers(request.headers), body: request.body, redirect: 'follow' }));",
" }",
" } catch (error) {}",
" }",
" return new Response('Down', { status: 503 });",
" }",
" return await getValidResponse(request, domainBackend);",
" }",
"};",
"async function ADD(envadd) {",
" var addtext = envadd.replace(/[\\t |\"'`\\r\\n]+/g, ',').replace(/,+/g, ',');",
" if (addtext.charAt(0) == ',') addtext = addtext.slice(1);",
" if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, -1);",
" return addtext.split(',');",
"}",
].join("\n");
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (request.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
if (url.pathname === "/deploy-pages" && request.method === "POST") return handleDeployPages(request);
if (url.pathname.startsWith("/api/")) {
const apiPath = url.pathname.replace("/api/", "/");
const apiUrl = CF_API + apiPath + url.search;
try {
const init = {
method: request.method,
headers: {
"X-Auth-Email": request.headers.get("X-Auth-Email") || "",
"X-Auth-Key": request.headers.get("X-Auth-Key") || "",
"Content-Type": "application/json",
},
};
if (request.method !== "GET" && request.method !== "DELETE") init.body = await request.text();
const response = await fetch(apiUrl, init);
const data = await response.text();
return new Response(data, { status: response.status, headers: Object.assign({}, corsHeaders, { "Content-Type": "application/json" }) });
} catch (err) {
return new Response(JSON.stringify({ error: err.message }), { status: 500, headers: corsHeaders });
}
}
return new Response(htmlDashboard(), { headers: { "Content-Type": "text/html; charset=utf-8" } });
},
};
async function handleDeployPages(request) {
try {
const body = await request.json();
const { email, apiKey, projectName, workerJs, backends, useCustomScript } = body;
if (!email || !apiKey) throw new Error("Email & API Key required");
if (!projectName) throw new Error("projectName required");
const result = { success: true, steps: [], url: null, message: "" };
result.steps.push("Getting account...");
const accRes = await fetch(CF_API + "/accounts", { headers: { "X-Auth-Email": email, "X-Auth-Key": apiKey } });
const accData = await accRes.json();
if (!accData.success || !accData.result.length) throw new Error("Invalid credentials");
const accountId = accData.result[0].id;
result.steps.push("Account: " + accData.result[0].name);
let script = "";
if (useCustomScript && workerJs) {
script = workerJs;
result.steps.push("Using CUSTOM script (" + script.length + " bytes)");
}
if (!script) {
script = DEFAULT_TEMPLATE_LINES;
if (backends && backends.length > 0) {
const arr = backends.map(function (b) { return "'" + b.trim() + "'"; }).join(", ");
script = script.replace(/let domainBackend = \['payload\.goku25\.workers\.dev'\];/, "let domainBackend = [" + arr + "];");
}
}
result.steps.push("Checking project...");
const checkRes = await fetch(CF_API + "/accounts/" + accountId + "/pages/projects/" + projectName, { headers: { "X-Auth-Email": email, "X-Auth-Key": apiKey } });
if (!(await checkRes.json()).success) {
await fetch(CF_API + "/accounts/" + accountId + "/pages/projects", {
method: "POST",
headers: { "X-Auth-Email": email, "X-Auth-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ name: projectName, production_branch: "main" }),
});
}
result.steps.push("Deploying...");
const boundary = "----Boundary" + Date.now().toString(36);
let body2 = "--" + boundary + '\r\nContent-Disposition: form-data; name="manifest"\r\n\r\n{"name":"' + projectName + '"}\r\n';
body2 += "--" + boundary + '\r\nContent-Disposition: form-data; name="_worker.js"; filename="_worker.js"\r\nContent-Type: application/javascript\r\n\r\n' + script + "\r\n";
body2 += "--" + boundary + "--\r\n";
const deployRes = await fetch(CF_API + "/accounts/" + accountId + "/pages/projects/" + projectName + "/deployments", {
method: "POST",
headers: { "X-Auth-Email": email, "X-Auth-Key": apiKey, "Content-Type": "multipart/form-data; boundary=" + boundary },
body: body2,
});
const deployData = await deployRes.json();
if (deployData.success) {
result.url = "https://" + projectName + ".pages.dev";
result.message = "Deployed!";
} else {
result.success = false;
result.message = JSON.stringify(deployData.errors);
}
return new Response(JSON.stringify(result), { status: 200, headers: Object.assign({}, corsHeaders, { "Content-Type": "application/json" }) });
} catch (err) {
return new Response(JSON.stringify({ success: false, message: err.message }), { status: 500, headers: Object.assign({}, corsHeaders, { "Content-Type": "application/json" }) });
}
}
function htmlDashboard() {
const templatesB64 = btoa(unescape(encodeURIComponent(JSON.stringify(TEMPLATES))));
const defaultB64 = btoa(unescape(encodeURIComponent(DEFAULT_TEMPLATE_LINES)));
const HTML_TOP = `
Unified CF Pages Toolkit
Bulk Import CF Accounts
Format: email (enter) apikey (double enter) next account
⚠️
Bulk Delete Confirmation
You are about to delete 0 domain(s)
TEMPLATE: DEFAULT
⚠️ Mode CUSTOM: script di bawah ini yang akan di-deploy apa adanya.
Inventory Domains
Vault Empty
Import CF accounts from the sidebar to begin.
`;
const SCRIPT_OPEN = "";
const SCRIPT_CLOSE = "";
const SCRIPT_BODY = SCRIPT_OPEN + `
var TEMPLATES_B64 = "${templatesB64}";
var DEFAULT_B64 = "${defaultB64}";
function b64d(s) { return decodeURIComponent(escape(atob(s))); }
var TEMPLATES = JSON.parse(b64d(TEMPLATES_B64));
var DEFAULT_SCRIPT = b64d(DEFAULT_B64);
var accounts = JSON.parse(localStorage.getItem('cf_accounts') || '[]');
var showEmailStatus = false;
var currentIdx = 0;
var selectedDomains = new Set();
var domainListData = [];
var currentProject = null;
var dnsConfig = JSON.parse(localStorage.getItem('cf_dns_config') || 'null') || {
email: '', apiKey: '', zoneId: null, zoneName: '', autoSync: false, ttl: 1, cachedZones: []
};
var currentTemplate = localStorage.getItem('cf_tpl') || 'default';
var deployMode = localStorage.getItem('cf_deploy_mode') || 'template';
var sidebarClosed = localStorage.getItem('cf_sidebar') === 'closed';
function saveDnsConfig() { localStorage.setItem('cf_dns_config', JSON.stringify(dnsConfig)); }
function applySidebarState() {
document.body.classList.toggle('sb-closed', sidebarClosed);
}
function toggleSidebar() {
sidebarClosed = !sidebarClosed;
localStorage.setItem('cf_sidebar', sidebarClosed ? 'closed' : 'open');
applySidebarState();
}
function switchTab(tab) {
document.getElementById('tabDeployer').classList.toggle('hidden', tab !== 'deployer');
document.getElementById('tabDomains').classList.toggle('hidden', tab !== 'domains');
document.getElementById('tabBtnDeployer').classList.toggle('active', tab === 'deployer');
document.getElementById('tabBtnDomains').classList.toggle('active', tab === 'domains');
if (tab === 'deployer') renderDeployerAccountSelect();
}
function setDeployMode(mode) {
deployMode = mode;
localStorage.setItem('cf_deploy_mode', mode);
document.getElementById('modeBtnD').classList.toggle('active', mode === 'template');
document.getElementById('modeBtnC').classList.toggle('active', mode === 'custom');
document.getElementById('templatePanel').classList.toggle('hidden', mode !== 'template');
document.getElementById('customPanel').classList.toggle('hidden', mode !== 'custom');
var ta = document.getElementById('scriptEditD');
var hint = document.getElementById('editHint');
if (mode === 'custom') {
ta.readOnly = false;
ta.style.color = '#a3e635';
hint.textContent = '(editable)';
hint.className = 'text-lime-400';
} else {
ta.readOnly = true;
ta.style.color = '#fbbf24';
hint.textContent = '(auto-generated)';
hint.className = 'text-amber-400';
loadTemplate(currentTemplate);
}
}
function getBackendsD() {
return document.getElementById('backendListD').value
.split('\\n').map(function(s) { return s.trim(); })
.filter(function(s) { return s.length > 0 && s.indexOf('.') !== -1; });
}
function buildScriptFromTemplate(tpl, backends) {
if (tpl === 'default' || !TEMPLATES[tpl]) {
var s = DEFAULT_SCRIPT;
if (backends.length) {
var arr = backends.map(function(b) { return "'" + b + "'"; }).join(", ");
s = s.replace(/let domainBackend = \\['payload\\.goku25\\.workers\\.dev'\\];/, "let domainBackend = [" + arr + "];");
}
return s;
}
var tplStr = TEMPLATES[tpl];
var arrLines = backends.map(function(b) { return JSON.stringify(b); }).join(",\\n");
return tplStr.replace("__BACKENDS__", backends.length ? arrLines : "");
}
function loadTemplate(tpl) {
currentTemplate = tpl;
localStorage.setItem('cf_tpl', tpl);
if (deployMode !== 'template') return;
var be = getBackendsD();
var s = buildScriptFromTemplate(tpl, be);
document.getElementById('scriptEditD').value = s;
document.querySelectorAll('.tpl-btn').forEach(function(b) { b.classList.toggle('active', b.getAttribute('data-tpl') === tpl); });
document.getElementById('tplStatus').textContent = 'TEMPLATE: ' + tpl.toUpperCase();
}
function refreshPreview() {
if (deployMode === 'template') loadTemplate(currentTemplate);
else alert('Mode CUSTOM: editor bebas, tidak ada refresh preview.');
}
function resetDeployScript() {
if (deployMode === 'custom') {
document.getElementById('scriptEditD').value = '';
} else {
loadTemplate(currentTemplate);
}
}
function renderDeployerAccountSelect() {
var sel = document.getElementById('accSelectD');
sel.innerHTML = accounts.map(function(acc, i) {
return '';
}).join('') || '';
}
async function deployPages() {
var idx = document.getElementById('accSelectD').value;
if (idx === '' || !accounts[idx]) { alert('Pilih akun dulu'); return; }
var acc = accounts[idx];
var pn = document.getElementById('deployProjectName').value.trim();
if (!pn) { alert('Isi project name'); return; }
var backends = getBackendsD();
var script;
if (deployMode === 'custom') {
script = document.getElementById('scriptEditD').value;
if (!script.trim()) { alert('Script custom kosong'); return; }
} else {
var badBackend = backends.find(function(b) { return /["'\\\\\\s]/.test(b); });
if (badBackend) { alert('Backend tidak valid: ' + badBackend); return; }
script = buildScriptFromTemplate(currentTemplate, backends);
}
if (!script) { alert('Gagal build script'); return; }
var btn = document.getElementById('btnDeployPages');
var out = document.getElementById('deployResult');
btn.disabled = true; btn.textContent = 'DEPLOYING...';
out.classList.remove('hidden');
out.innerHTML = '>> Deploying ' + pn + ' | mode=' + deployMode + ' | tpl=' + (deployMode === 'template' ? currentTemplate : '-') + ' | size=' + script.length + 'B
';
try {
var r = await fetch('/deploy-pages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: acc.email, apiKey: acc.key, projectName: pn,
workerJs: script, backends: backends, useCustomScript: true
})
});
var txt = await r.text();
var d;
try { d = JSON.parse(txt); } catch (parseErr) {
out.innerHTML += '✗ Non-JSON response (HTTP ' + r.status + '):
' + txt.substring(0, 500).replace(/';
btn.disabled = false; btn.textContent = 'Deploy Pages';
return;
}
if (d.steps) d.steps.forEach(function(s) { out.innerHTML += '>> ' + s + '
'; });
if (d.success) {
out.innerHTML += '✓ SUCCESS: ' + d.url + '';
} else {
out.innerHTML += '✗ FAILED: ' + d.message + '';
}
} catch (e) {
out.innerHTML += '✗ ERROR: ' + e.message + '';
}
out.scrollTop = out.scrollHeight;
btn.disabled = false; btn.textContent = 'Deploy Pages';
}
window.addEventListener('load', function() {
try {
applySidebarState();
renderDnsPanel();
renderSyncStrip();
renderDeployerAccountSelect();
setDeployMode(deployMode);
loadTemplate(currentTemplate);
if (accounts.length > 0) {
renderAll();
handleMainAccountChange();
} else {
var aa = document.getElementById('actionArea'); if (aa) aa.classList.add('hidden');
var es = document.getElementById('emptyState'); if (es) es.classList.remove('hidden');
}
document.querySelectorAll('.tpl-btn').forEach(function(b) {
b.onclick = function() { loadTemplate(b.getAttribute('data-tpl')); };
});
document.getElementById('backendListD').addEventListener('input', function() {
if (deployMode === 'template') loadTemplate(currentTemplate);
});
} catch (e) { console.error('Boot error:', e); }
});
function toggleBulkModal(show) { document.getElementById('bulkModal').classList.toggle('hidden', !show); }
function toggleNcModal(show) { document.getElementById('ncModal').classList.toggle('hidden', !show); }
function toggleBulkDeleteModal(show) {
if (show && selectedDomains.size === 0) { alert('Pilih domain dulu'); return; }
document.getElementById('bulkDeleteModal').classList.toggle('hidden', !show);
if (show) updateBulkDeleteModal();
}
function toggleShowEmails() {
showEmailStatus = !showEmailStatus;
var t1 = document.getElementById('eyeText'); if (t1) t1.innerText = showEmailStatus ? 'Hide Email' : 'Show Email';
var i1 = document.getElementById('eyeIcon'); if (i1) i1.innerText = showEmailStatus ? '🙈' : '👁️';
var t2 = document.getElementById('eyeText2'); if (t2) t2.innerText = showEmailStatus ? 'Hide Email' : 'Show Email';
var i2 = document.getElementById('eyeIcon2'); if (i2) i2.innerText = showEmailStatus ? '🙈' : '👁️';
renderAll();
renderDeployerAccountSelect();
renderDnsPanel();
}
function maskEmail(email) {
if (!email) return "";
var parts = email.split('@');
return parts[0].substring(0, 4) + "****";
}
function copyToClipboard(text, label) {
navigator.clipboard.writeText(text).then(function() { alert(label + " copied!"); });
}
function renderAll() { renderMainSelector(); renderSidebarDetails(); }
function renderMainSelector() {
var select = document.getElementById('accSelect');
select.innerHTML = accounts.map(function(acc, index) {
return '';
}).join('');
}
function renderSidebarDetails() {
var list = document.getElementById('accountListDetails');
list.innerHTML = accounts.map(function(acc, index) {
var activeCls = index == currentIdx ? 'border-indigo-500 ring-1 ring-indigo-500/50' : 'border-slate-800/50';
var labelCls = index == currentIdx ? 'text-indigo-400' : 'text-slate-500';
return '' +
'
' +
'
' + acc.label + '' +
'
' +
'' +
'' +
'' +
'
' +
'
' +
'
' +
(showEmailStatus ? acc.email : maskEmail(acc.email)) +
'
' +
'
';
}).join('');
}
function configureDns() {
document.getElementById('ncEmail').value = dnsConfig.email || '';
document.getElementById('ncToken').value = dnsConfig.apiKey || '';
document.getElementById('ncAutoSync').checked = !!dnsConfig.autoSync;
document.getElementById('ncTtl').value = dnsConfig.ttl || 1;
var sel = document.getElementById('ncDomain');
if (dnsConfig.cachedZones && dnsConfig.cachedZones.length) {
sel.innerHTML = dnsConfig.cachedZones.map(function(z) {
return '';
}).join('');
} else if (dnsConfig.zoneId) {
sel.innerHTML = '';
}
document.getElementById('ncModalMsg').textContent = '';
toggleNcModal(true);
}
async function fetchCFZones() {
var email = document.getElementById('ncEmail').value.trim();
var key = document.getElementById('ncToken').value.trim();
var msg = document.getElementById('ncModalMsg');
var sel = document.getElementById('ncDomain');
if (!email || !key) { msg.textContent = '⚠️ Email & Global API Key wajib'; return; }
msg.textContent = '⏳ Loading Cloudflare zones...';
try {
var r = await fetch('/api/zones?per_page=50', {
headers: { 'X-Auth-Email': email, 'X-Auth-Key': key }
});
var data = await r.json();
if (!data.success) {
msg.textContent = '❌ ' + ((data.errors && data.errors[0] && data.errors[0].message) || 'HTTP ' + r.status);
return;
}
var zones = data.result || [];
if (!zones.length) { msg.textContent = '⚠️ Tidak ada zone di akun ini'; return; }
dnsConfig.cachedZones = zones.map(function(z) { return { id: z.id, name: z.name }; });
sel.innerHTML = zones.map(function(z) {
return '';
}).join('');
msg.textContent = '✅ ' + zones.length + ' zone dimuat';
} catch (e) { msg.textContent = '❌ ' + e.message; }
}
function saveDnsSettings() {
var email = document.getElementById('ncEmail').value.trim();
var apiKey = document.getElementById('ncToken').value.trim();
var zoneSel = document.getElementById('ncDomain').value;
var autoSync = document.getElementById('ncAutoSync').checked;
var ttl = Number(document.getElementById('ncTtl').value) || 1;
var msg = document.getElementById('ncModalMsg');
if (!email || !apiKey) { msg.textContent = '⚠️ Email & API Key wajib diisi'; return; }
if (!zoneSel) { msg.textContent = '⚠️ Pilih zone dulu'; return; }
var parts = zoneSel.split('|');
dnsConfig.email = email;
dnsConfig.apiKey = apiKey;
dnsConfig.zoneId = parts[0];
dnsConfig.zoneName = parts[1];
dnsConfig.autoSync = autoSync;
dnsConfig.ttl = ttl;
saveDnsConfig();
renderDnsPanel();
renderSyncStrip();
toggleNcModal(false);
}
function clearDnsSettings() {
if (!confirm('Hapus konfigurasi Cloudflare DNS?')) return;
dnsConfig = { email: '', apiKey: '', zoneId: null, zoneName: '', autoSync: false, ttl: 1, cachedZones: [] };
saveDnsConfig();
renderDnsPanel();
renderSyncStrip();
toggleNcModal(false);
}
function renderDnsPanel() {
var box = document.getElementById('ncStatus');
if (!box) return;
if (!dnsConfig.email || !dnsConfig.zoneId) {
box.innerHTML = 'Belum dikonfigurasi. Klik CONFIGURE.
';
return;
}
box.innerHTML = '' +
'' +
'Cloudflare DNS' +
'
' +
'' + (showEmailStatus ? dnsConfig.email : maskEmail(dnsConfig.email)) + '
' +
'🌐 ' + dnsConfig.zoneName + '
' +
'';
}
function toggleAutoSync(val) {
dnsConfig.autoSync = val;
saveDnsConfig();
renderSyncStrip();
}
function renderSyncStrip() {
var el = document.getElementById('syncStrip');
if (!el) return;
if (!dnsConfig.email || !dnsConfig.zoneId) {
el.innerHTML = '⚠️ CF DNS belum dikonfigurasi —Configure';
return;
}
var target = currentProject ? (currentProject.subdomain || (currentProject.name + '.pages.dev')) : '(pilih project)';
var active = dnsConfig.autoSync ? '● AKTIF' : '○ mati';
el.innerHTML = '🌐 CF DNS Zone: ' + dnsConfig.zoneName + '' +
'|' +
'🎯 Target: ' + target + '' +
'|' +
'Auto-sync: ' + active + '';
}
function toggleSelectAll() {
if (selectedDomains.size === domainListData.length && domainListData.length > 0) {
selectedDomains.clear();
} else {
domainListData.forEach(function(d) { selectedDomains.add(d.name); });
}
updateDomainCheckboxes();
updateBulkDeleteButton();
}
function toggleDomainSelection(domainName) {
if (selectedDomains.has(domainName)) selectedDomains.delete(domainName);
else selectedDomains.add(domainName);
updateBulkDeleteButton();
updateSelectAllButton();
}
function updateDomainCheckboxes() {
document.querySelectorAll('.domain-checkbox').forEach(function(cb) {
cb.checked = selectedDomains.has(cb.value);
});
updateSelectAllButton();
}
function updateSelectAllButton() {
var btn = document.getElementById('btnSelectAll');
if (!btn) return;
if (domainListData.length === 0) btn.innerText = 'SELECT ALL';
else if (selectedDomains.size === domainListData.length) btn.innerText = 'DESELECT ALL';
else btn.innerText = 'SELECT ALL';
}
function updateBulkDeleteButton() {
var btn = document.getElementById('btnBulkDelete');
if (!btn) return;
if (selectedDomains.size > 0) {
btn.classList.remove('hidden');
btn.innerText = 'DELETE SELECTED (' + selectedDomains.size + ')';
} else btn.classList.add('hidden');
}
function updateBulkDeleteModal() {
document.getElementById('deleteCount').innerText = selectedDomains.size;
document.getElementById('deletePreview').innerHTML = Array.from(selectedDomains).map(function(d) {
return '🌐 ' + d + '
';
}).join('');
}
async function handleMainAccountChange() {
currentIdx = document.getElementById('accSelect').value;
selectedDomains.clear();
updateBulkDeleteButton();
renderAll();
await fetchProjects();
}
function getBaseHeaders() {
var acc = accounts[currentIdx];
return { 'X-Auth-Email': acc.email, 'X-Auth-Key': acc.key };
}
async function fetchProjects() {
try {
var res = await fetch('/api/accounts', { headers: getBaseHeaders() });
var data = await res.json();
if (!data.success) throw new Error((data.errors && data.errors[0] && data.errors[0].message) || 'CF error');
var cloudflareAccountId = data.result[0].id;
var projRes = await fetch('/api/accounts/' + cloudflareAccountId + '/pages/projects', { headers: getBaseHeaders() });
var projData = await projRes.json();
window.__cfAccountId = cloudflareAccountId;
var projSelect = document.getElementById('projSelect');
if (projData.result && projData.result.length > 0) {
projSelect.innerHTML = projData.result.map(function(p) {
return '';
}).join('');
document.getElementById('actionArea').classList.remove('hidden');
await fetchDomains();
} else {
projSelect.innerHTML = '';
document.getElementById('actionArea').classList.add('hidden');
}
} catch (e) { alert("Auth Failure: " + e.message); }
}
async function fetchDomains() {
var sel = document.getElementById('projSelect');
var val = sel.value;
if (!val || val === 'No Projects Found') return;
var parts = val.split('/');
var accId = parts[0];
var projName = parts[1];
var subdomain = sel.options[sel.selectedIndex].dataset.subdomain || (projName + '.pages.dev');
currentProject = { name: projName, subdomain: subdomain };
renderSyncStrip();
var listEl = document.getElementById('domainList');
listEl.innerHTML = 'PULLING DATA...
';
try {
var res = await fetch('/api/accounts/' + accId + '/pages/projects/' + projName + '/domains', { headers: getBaseHeaders() });
var data = await res.json();
domainListData = data.result || [];
selectedDomains.clear();
updateBulkDeleteButton();
updateSelectAllButton();
listEl.innerHTML = domainListData.map(function(d) {
return '' +
'' +
'
' +
'
' +
'
' + d.name + '
' +
'
' +
'' +
'' + d.status + '' +
'
' +
'
' +
'
' +
'' +
'';
}).join('') || 'No Domains Active';
} catch (e) {
listEl.innerHTML = 'CONNECTION ERROR';
domainListData = [];
}
}
async function deleteCurrentProject() {
var sel = document.getElementById('projSelect');
var val = sel.value;
if (!val || val === 'No Projects Found') { alert('Tidak ada project terpilih'); return; }
var parts = val.split('/');
var accId = parts[0];
var projName = parts[1];
if (!confirm('HAPUS PROJECT CF PAGES "' + projName + '"?\\n\\nSemua custom domain yang nempel di project ini akan ikut terhapus.\\nIni TIDAK BISA dibatalkan!')) return;
if (!confirm('Konfirmasi sekali lagi: hapus project "' + projName + '"?')) return;
try {
var dr = await fetch('/api/accounts/' + accId + '/pages/projects/' + projName, {
method: 'DELETE',
headers: getBaseHeaders()
});
var dd = await dr.json();
if (dd.success) {
alert('✓ Project "' + projName + '" berhasil dihapus');
await fetchProjects();
document.getElementById('actionArea').classList.add('hidden');
} else {
alert('✗ Gagal hapus: ' + JSON.stringify(dd.errors || dd));
}
} catch (e) {
alert('✗ Error: ' + e.message);
}
}
function matchCFZone(fullDomain) {
var zones = dnsConfig.cachedZones || [];
var best = null;
for (var i = 0; i < zones.length; i++) {
var z = zones[i];
if (fullDomain === z.name || fullDomain.endsWith('.' + z.name)) {
if (!best || z.name.length > best.name.length) best = z;
}
}
if (!best) return null;
var recordName;
if (fullDomain === best.name) recordName = '@';
else recordName = fullDomain.slice(0, fullDomain.length - best.name.length - 1);
return { zoneId: best.id, zone: best.name, recordName: recordName };
}
function dnsHeaders() {
return {
'X-Auth-Email': dnsConfig.email,
'X-Auth-Key': dnsConfig.apiKey,
'Content-Type': 'application/json'
};
}
async function cfAddDnsRecord(fullDomain, target) {
var r = await fetch('/api/zones/' + dnsConfig.zoneId + '/dns_records', {
method: 'POST',
headers: dnsHeaders(),
body: JSON.stringify({
type: 'CNAME',
name: fullDomain,
content: target,
ttl: dnsConfig.ttl || 1,
proxied: false
})
});
var data = await r.json();
return { ok: r.ok && data.success, data: data };
}
async function cfDeleteDnsRecordByMatch(fullDomain) {
var match = matchCFZone(fullDomain);
if (!match) return { ok: false, error: 'no match' };
try {
var listRes = await fetch('/api/zones/' + dnsConfig.zoneId + '/dns_records?type=CNAME&per_page=100', {
headers: { 'X-Auth-Email': dnsConfig.email, 'X-Auth-Key': dnsConfig.apiKey }
});
var listData = await listRes.json();
var records = (listData && listData.result) || [];
var rec = records.find(function(r) {
return r.type === 'CNAME' && (r.name === fullDomain || (match.recordName === '@' && r.name === match.zone));
});
if (!rec) return { ok: false, error: 'record not found' };
var dr = await fetch('/api/zones/' + dnsConfig.zoneId + '/dns_records/' + rec.id, {
method: 'DELETE',
headers: { 'X-Auth-Email': dnsConfig.email, 'X-Auth-Key': dnsConfig.apiKey }
});
var dd = await dr.json();
return { ok: dr.ok && dd.success };
} catch (e) { return { ok: false, error: e.message }; }
}
async function handleBulkAdd() {
var list = document.getElementById('bulkList').value.split('\\n').map(function(s) { return s.trim(); }).filter(Boolean);
var sel = document.getElementById('projSelect');
var parts = sel.value.split('/');
var accId = parts[0];
var projName = parts[1];
var target = sel.options[sel.selectedIndex].dataset.subdomain || (projName + '.pages.dev');
var btn = document.getElementById('btnBulk');
var progress = document.getElementById('progress');
if (list.length === 0) { alert('Daftar kosong'); return; }
var syncOn = dnsConfig.autoSync && dnsConfig.email && dnsConfig.apiKey && dnsConfig.zoneId;
btn.disabled = true; btn.classList.add('opacity-50');
progress.classList.remove('hidden');
progress.innerHTML = '>> BOOTING SEQUENCE... ' + (syncOn ? '(CF DNS sync ON)' : '(CF DNS sync OFF)') + '
';
var okCF = 0, okNC = 0, failCF = 0, failNC = 0, skipNC = 0;
for (var i = 0; i < list.length; i++) {
var domain = list[i].trim();
progress.innerHTML += '>> ' + domain + '
';
progress.scrollTop = progress.scrollHeight;
progress.innerHTML += ' CF Pages ... ';
try {
var headers = getBaseHeaders();
headers['Content-Type'] = 'application/json';
var res = await fetch('/api/accounts/' + accId + '/pages/projects/' + projName + '/domains', {
method: 'POST',
headers: headers,
body: JSON.stringify({ name: domain })
});
var data = await res.json();
if (data.success) { okCF++; progress.innerHTML += '✓ Success
'; }
else { failCF++; progress.innerHTML += '✗ ' + ((data.errors && data.errors[0] && data.errors[0].message) || 'Fail') + '
'; }
} catch (e) { failCF++; progress.innerHTML += '✗ ' + e.message + '
'; }
progress.scrollTop = progress.scrollHeight;
if (syncOn) {
progress.innerHTML += ' CF DNS ... ';
var match = matchCFZone(domain);
if (!match) { skipNC++; progress.innerHTML += '⊘ tidak match zone
'; }
else {
try {
var r = await cfAddDnsRecord(domain, target);
if (r.ok) {
okNC++;
var rid = r.data && r.data.result ? r.data.result.id : '';
progress.innerHTML += '✓ ' + match.recordName + ' → ' + target + (rid ? ' (#' + rid.substring(0,8) + ')' : '') + '
';
} else {
failNC++;
var errMsg = (r.data && r.data.errors && r.data.errors[0] && r.data.errors[0].message) || 'fail';
progress.innerHTML += '✗ ' + errMsg + '
';
}
} catch (e) { failNC++; progress.innerHTML += '✗ ' + e.message + '
'; }
}
progress.scrollTop = progress.scrollHeight;
}
}
btn.disabled = false; btn.classList.remove('opacity-50');
progress.innerHTML += '
>> DONE
';
progress.innerHTML += 'CF Pages: ✓ ' + okCF + ' ✗ ' + failCF + '
';
if (syncOn) progress.innerHTML += 'CF DNS: ✓ ' + okNC + ' ✗ ' + failNC + ' ⊘ ' + skipNC + '
';
progress.scrollTop = progress.scrollHeight;
await fetchDomains();
}
async function deleteDomain(name) {
if (!confirm('Delete ' + name + ' from CF Pages?')) return;
var sel = document.getElementById('projSelect');
var parts = sel.value.split('/');
try {
var res = await fetch('/api/accounts/' + parts[0] + '/pages/projects/' + parts[1] + '/domains/' + name, { method: 'DELETE', headers: getBaseHeaders() });
var data = await res.json();
if (data.success) { selectedDomains.delete(name); updateBulkDeleteButton(); }
} catch (e) { console.error(e); }
await fetchDomains();
}
async function executeBulkDelete() {
var sel = document.getElementById('projSelect');
var parts = sel.value.split('/');
var progress = document.getElementById('progress');
var alsoDns = document.getElementById('alsoNodelocDelete').checked;
var domainsToDelete = Array.from(selectedDomains);
toggleBulkDeleteModal(false);
progress.classList.remove('hidden');
progress.innerHTML = '>> BULK DELETE SEQUENCE...' + (alsoDns ? ' (CF DNS ON)' : '') + '
';
for (var i = 0; i < domainsToDelete.length; i++) {
var domain = domainsToDelete[i];
progress.innerHTML += '>> ' + domain + ' ';
try {
var res = await fetch('/api/accounts/' + parts[0] + '/pages/projects/' + parts[1] + '/domains/' + domain, { method: 'DELETE', headers: getBaseHeaders() });
var data = await res.json();
progress.innerHTML += data.success ? 'CF ✓ ' : 'CF ✗ ';
} catch (e) { progress.innerHTML += 'CF ERR '; }
if (alsoDns && dnsConfig.email && dnsConfig.apiKey && dnsConfig.zoneId) {
var r = await cfDeleteDnsRecordByMatch(domain);
progress.innerHTML += r.ok ? 'DNS ✓' : 'DNS ⊘';
}
progress.innerHTML += '
';
progress.scrollTop = progress.scrollHeight;
}
selectedDomains.clear();
updateBulkDeleteButton();
await fetchDomains();
progress.innerHTML += '>> BULK DELETE COMPLETED
';
}
function processBulkAccounts() {
var raw = document.getElementById('bulkAccountInput').value.trim();
if (!raw) return;
var lines = raw.split('\\n').map(function(l) { return l.trim(); }).filter(function(l) { return l !== ''; });
var newAccs = [];
for (var i = 0; i < lines.length; i += 2) {
var email = lines[i], key = lines[i + 1];
if (email && key) newAccs.push({ label: 'Profile ' + (accounts.length + newAccs.length + 1), email: email, key: key });
}
if (newAccs.length > 0) {
accounts = accounts.concat(newAccs);
localStorage.setItem('cf_accounts', JSON.stringify(accounts));
location.reload();
}
}
function deleteAccount(idx) {
if (!confirm('Hapus profile ini?')) return;
accounts.splice(idx, 1);
localStorage.setItem('cf_accounts', JSON.stringify(accounts));
location.reload();
}
function copyAllAccounts() {
var text = accounts.map(function(a) { return a.email + '\\n' + a.key; }).join('\\n\\n');
copyToClipboard(text, 'Vault backup');
}
function clearAllData() {
if (!confirm('DANGER: Hapus semua akun CF + konfigurasi CF DNS?')) return;
localStorage.clear();
location.reload();
}
` + SCRIPT_CLOSE;
return HTML_TOP + SCRIPT_BODY + `
`;
}