diff --git a/backend/src/adapters/cpanel.js b/backend/src/adapters/cpanel.js index 7a27743..4486029 100644 --- a/backend/src/adapters/cpanel.js +++ b/backend/src/adapters/cpanel.js @@ -76,9 +76,13 @@ async function parseResponse(res, debug = false) { if (debug) console.log('[cpanel] full envelope:', JSON.stringify(envelope, null, 2)); if (res.status >= 400) throw new Error(`cPanel HTTP ${res.status}`); - // UAPI can nest the real payload under result{} or return it flat + // UAPI wraps the payload in result{} but some installs omit it const result = envelope.result ?? envelope; - if (result.status === 0) throw new Error(`cPanel UAPI error: ${result.errors?.join(', ') ?? 'Unknown error'}`); + const statusVal = result.status ?? result.result; + if (statusVal === 0 || statusVal === '0') { + const msg = result.errors?.join(', ') ?? result.error ?? result.message ?? 'Unknown UAPI error'; + throw new Error(`cPanel UAPI error: ${msg}`); + } return result.data; } @@ -137,29 +141,49 @@ async function api2(func, params = {}, debug = false) { // ─── Public API ─────────────────────────────────────────────────────────────── async function listZones() { - // Try DNS::list_zones first (cPanel v82+), fall back to DomainInfo::list_domains let domains = []; + // Try DNS::list_zones first (cPanel v82+). + // Fall back for ANY failure — different cPanel versions use different error messages. try { const data = await uapiGet('DNS', 'list_zones'); - if (data && data.length > 0) { - return data.map(z => { - const name = typeof z === 'string' ? z : (z.domain ?? z.zone ?? z.name); + if (Array.isArray(data) && data.length > 0) { + const zones = data.map(z => { + const raw = typeof z === 'string' ? z : (z.domain ?? z.zone ?? z.name ?? ''); + const name = raw.replace(/\.$/, ''); // strip trailing dot if present return { id: name, name }; - }); + }).filter(z => z.name); + if (zones.length > 0) return zones; } } catch (err) { - if (!err.message.includes('could not find the function')) throw err; - // Function doesn't exist on this cPanel version — use DomainInfo fallback + console.log('[cpanel] DNS::list_zones unavailable, falling back to DomainInfo:', err.message); } - const info = await uapiGet('DomainInfo', 'list_domains'); - // Returns { main_domain, addon_domains, parked_domains, sub_domains } - if (info) { - const main = info.main_domain ? [info.main_domain] : []; - const addon = Array.isArray(info.addon_domains) ? info.addon_domains : []; - const parked = Array.isArray(info.parked_domains) ? info.parked_domains : []; - domains = [...new Set([...main, ...addon, ...parked])]; + // Fallback: DomainInfo::list_domains (available on all cPanel versions) + try { + const info = await uapiGet('DomainInfo', 'list_domains'); + if (info) { + const main = info.main_domain ? [info.main_domain] : []; + const addon = Array.isArray(info.addon_domains) ? info.addon_domains : []; + const parked = Array.isArray(info.parked_domains) ? info.parked_domains : []; + // sub_domains are sub.domain.tld — only include the apex, not subs + domains = [...new Set([...main, ...addon, ...parked])].filter(Boolean); + } + } catch (err) { + console.error('[cpanel] DomainInfo::list_domains failed:', err.message); + } + + if (domains.length === 0) { + // Last resort: Zone::listzone lists the raw zone names (some legacy cPanel installs) + try { + const data = await uapiGet('Zone', 'listzone'); + if (Array.isArray(data)) { + domains = data.map(z => { + const raw = typeof z === 'string' ? z : (z.domain ?? z.zone ?? z.name ?? ''); + return raw.replace(/\.$/, ''); + }).filter(Boolean); + } + } catch { /* ignore */ } } return domains.map(d => ({ id: d, name: d }));