{"id":32748,"date":"2025-10-28T22:55:50","date_gmt":"2025-10-28T15:55:50","guid":{"rendered":"https:\/\/dps.media\/?page_id=32748"},"modified":"2025-10-28T22:55:53","modified_gmt":"2025-10-28T15:55:53","slug":"cloudflare-dns-zone-total-converter-csv-xlsx-zone","status":"publish","type":"page","link":"https:\/\/dps.media\/en\/cloudflare-dns-zone-total-converter-csv-xlsx-zone\/","title":{"rendered":"Cloudflare DNS Zone Total Converter (CSV\/XLSX\/Zone)"},"content":{"rendered":"<?xml encoding=\"utf-8\" ?><h3 class=\"wp-block-heading\"><\/h3><!-- CSV\/XLSX \u2192 Cloudflare Zone Converter - DPS.MEDIA branded, CSS isolated --><div id=\"csvToZoneWidget\"><\/div><script>\r\n(function(){\r\n  const ROOT_ID = 'csvToZoneWidget';\r\n  const root = document.getElementById(ROOT_ID);\r\n  if (!root) return;\r\n\r\n  \/\/ Styles (isolated with #csvToZoneWidget prefix)\r\n  const styles = `\r\n  #${ROOT_ID} {\r\n    all: initial !important;\r\n    font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, Arial, sans-serif !important;\r\n  }\r\n  #${ROOT_ID} *, #${ROOT_ID} *::before, #${ROOT_ID} *::after {\r\n    box-sizing: border-box !important;\r\n    margin: 0 !important;\r\n    padding: 0 !important;\r\n    border: none !important;\r\n    background: none !important;\r\n    outline: none !important;\r\n    text-decoration: none !important;\r\n    list-style: none !important;\r\n    font: inherit !important;\r\n    color: inherit !important;\r\n    vertical-align: baseline !important;\r\n    line-height: normal !important;\r\n  }\r\n\r\n  \/* Container *\/\r\n  #${ROOT_ID} .cz-container {\r\n    max-width: 1000px !important;\r\n    margin: 24px auto !important;\r\n    border: 1px solid #e5e7eb !important;\r\n    border-radius: 16px !important;\r\n    overflow: hidden !important;\r\n    background: #fff !important;\r\n    box-shadow: 0 10px 25px -5px rgba(21,21,119,0.1), 0 4px 6px -2px rgba(21,21,119,0.05) !important;\r\n    font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, Arial, sans-serif !important;\r\n  }\r\n\r\n  \/* Header *\/\r\n  #${ROOT_ID} .cz-header {\r\n    background: linear-gradient(135deg, #151577 0%, #1e1e7a 100%) !important;\r\n    padding: 20px !important;\r\n    color: #fff !important;\r\n    position: relative !important;\r\n    overflow: hidden !important;\r\n  }\r\n  #${ROOT_ID} .cz-header-bg1 {\r\n    position: absolute !important; top: -20px !important; right: -20px !important;\r\n    width: 100px !important; height: 100px !important; background: rgba(255,255,255,0.05) !important; border-radius: 50% !important;\r\n  }\r\n  #${ROOT_ID} .cz-header-bg2 {\r\n    position: absolute !important; bottom: -30px !important; left: -30px !important;\r\n    width: 80px !important; height: 80px !important; background: rgba(50,181,97,0.15) !important; border-radius: 50% !important;\r\n  }\r\n  #${ROOT_ID} .cz-header-content { position: relative !important; z-index: 1 !important; }\r\n  #${ROOT_ID} .cz-header-top { display: flex !important; align-items: center !important; justify-content: space-between !important; margin-bottom: 8px !important; }\r\n  #${ROOT_ID} .cz-header-left { display: flex !important; align-items: center !important; gap: 12px !important; }\r\n  #${ROOT_ID} .cz-icon {\r\n    width: 40px !important; height: 40px !important; border-radius: 12px !important; display: flex !important; align-items: center !important; justify-content: center !important;\r\n    background: linear-gradient(135deg, #32b561, #28a555) !important; color: #fff !important; font-weight: 700 !important; font-size: 20px !important;\r\n    box-shadow: 0 4px 8px rgba(50,181,97,0.3) !important;\r\n  }\r\n  #${ROOT_ID} .cz-title { font-size: 20px !important; font-weight: 700 !important; letter-spacing: -0.5px !important; }\r\n  #${ROOT_ID} .cz-subtitle { font-size: 13px !important; opacity: 0.85 !important; }\r\n  #${ROOT_ID} .cz-brand { text-align: right !important; font-size: 12px !important; opacity: 0.75 !important; }\r\n  #${ROOT_ID} .cz-brand-name { color: #32b561 !important; font-weight: 700 !important; }\r\n  #${ROOT_ID} .cz-desc { font-size: 13px !important; opacity: 0.95 !important; }\r\n\r\n  \/* Main *\/\r\n  #${ROOT_ID} .cz-main { padding: 20px !important; }\r\n  #${ROOT_ID} .cz-actions { display: flex !important; flex-wrap: wrap !important; gap: 12px !important; align-items: end !important; margin-bottom: 12px !important; }\r\n  #${ROOT_ID} .cz-file {\r\n    padding: 12px !important; border: 2px dashed #cbd5e1 !important; border-radius: 12px !important; background: #f8fafc !important; color: #374151 !important;\r\n    display: flex !important; align-items: center !important; gap: 10px !important; flex: 1 !important; min-width: 240px !important;\r\n  }\r\n  #${ROOT_ID} .cz-file input[type=\"file\"] { display: none !important; }\r\n  #${ROOT_ID} .cz-btn { padding: 12px 18px !important; border-radius: 10px !important; cursor: pointer !important; font-weight: 700 !important; font-size: 13px !important; transition: all 0.25s !important; }\r\n  #${ROOT_ID} .cz-btn-primary { background: linear-gradient(135deg, #151577, #1e1e7a) !important; color: #fff !important; box-shadow: 0 4px 12px rgba(21,21,119,0.3) !important; }\r\n  #${ROOT_ID} .cz-btn-primary:hover { transform: translateY(-1px) !important; box-shadow: 0 6px 18px rgba(21,21,119,0.4) !important; }\r\n  #${ROOT_ID} .cz-btn-secondary { background: #fff !important; border: 2px solid #cbd5e1 !important; color: #374151 !important; }\r\n  #${ROOT_ID} .cz-btn-danger { background: #fff !important; border: 2px solid #dc2626 !important; color: #dc2626 !important; }\r\n  #${ROOT_ID} .cz-btn:disabled { background: #6b7280 !important; color: #fff !important; cursor: not-allowed !important; box-shadow: none !important; transform: none !important; }\r\n\r\n  #${ROOT_ID} .cz-statusbar { display: flex !important; gap: 10px !important; flex-wrap: wrap !important; align-items: center !important; padding: 12px !important; background: linear-gradient(90deg, #f8fafc, #f1f5f9) !important; border-radius: 12px !important; border: 1px solid #e2e8f0 !important; margin-bottom: 12px !important; }\r\n  #${ROOT_ID} .cz-status { font-size: 12px !important; font-weight: 600 !important; color: #151577 !important; margin-left: auto !important; }\r\n  #${ROOT_ID} .cz-badge { font-size: 12px !important; padding: 6px 10px !important; border-radius: 8px !important; background: #eef2ff !important; color: #151577 !important; border: 1px solid #c7d2fe !important; }\r\n  #${ROOT_ID} .cz-error { display: none !important; background: linear-gradient(135deg, #fef2f2, #fee2e2) !important; border: 2px solid #fca5a5 !important; color: #991b1b !important; padding: 12px !important; border-radius: 12px !important; margin-bottom: 12px !important; align-items: center !important; gap: 10px !important; }\r\n\r\n  \/* Output *\/\r\n  #${ROOT_ID} .cz-output { border: 2px solid #e5e7eb !important; border-radius: 12px !important; background: #fafafa !important; min-height: 160px !important; padding: 12px !important; overflow: auto !important; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important; font-size: 12px !important; white-space: pre !important; }\r\n  #${ROOT_ID} .cz-empty { text-align: center !important; color: #64748b !important; font-style: italic !important; }\r\n\r\n  \/* Toggle groups *\/\r\n  #${ROOT_ID} .cz-row { display: flex !important; flex-wrap: wrap !important; gap: 12px !important; align-items: center !important; margin-bottom: 12px !important; }\r\n  #${ROOT_ID} .cz-toggle { display: inline-flex !important; gap: 6px !important; background: #f1f5f9 !important; border: 1px solid #e2e8f0 !important; padding: 4px !important; border-radius: 10px !important; }\r\n  #${ROOT_ID} .cz-toggle-btn { padding: 8px 12px !important; border-radius: 8px !important; font-size: 12px !important; cursor: pointer !important; background: #fff !important; color: #374151 !important; border: 1px solid transparent !important; }\r\n  #${ROOT_ID} .cz-toggle-btn.active { background: linear-gradient(135deg, #151577, #1e1e7a) !important; color: #fff !important; border-color: #151577 !important; box-shadow: 0 4px 12px rgba(21,21,119,0.25) !important; }\r\n\r\n  \/* Footer *\/\r\n  #${ROOT_ID} .cz-footer { padding: 12px 20px !important; border-top: 1px solid #e5e7eb !important; background: #f9fafb !important; }\r\n  #${ROOT_ID} .cz-footer-content { display: flex !important; gap: 8px !important; align-items: center !important; justify-content: center !important; font-size: 12px !important; color: #6b7280 !important; }\r\n  #${ROOT_ID} .cz-footer-brand { color: #32b561 !important; font-weight: 700 !important; }\r\n  #${ROOT_ID} .cz-footer-link { color: #151577 !important; text-decoration: underline !important; }\r\n  `;\r\n\r\n  \/\/ Inject CSS into <head> (safer across WP themes)\r\n  const styleEl = document.createElement('style');\r\n  styleEl.textContent = styles;\r\n  document.head.appendChild(styleEl);\r\n\r\n  \/\/ HTML structure\r\n  root.innerHTML = `\r\n    <div class=\"cz-container\">\r\n      <div class=\"cz-header\">\r\n        <div class=\"cz-header-bg1\">\r\n        <div class=\"cz-header-bg2\">\r\n        <div class=\"cz-header-content\">\r\n          <div class=\"cz-header-top\">\r\n            <div class=\"cz-header-left\">\r\n              <div class=\"cz-icon\">\ud83e\udde9\r\n              <div>\r\n                <div class=\"cz-title\">CSV\/XLSX \u2192 Cloudflare Zone\r\n                <div class=\"cz-subtitle\">Chuy\u1ec3n t\u1eeb b\u1ea3ng DNS sang file BIND (Cloudflare)\r\n              \r\n            \r\n            <div class=\"cz-brand\">\r\n              <div class=\"cz-brand-name\">DPS.MEDIA\r\n              <div>Digital Tools\r\n            \r\n          \r\n          <div class=\"cz-desc\">Nh\u1eadp file CSV\/XLSX c\u00f3 c\u1ed9t: <strong>Host, <strong>Type, <strong>Data, <strong>TTL. K\u1ebft qu\u1ea3 gi\u1ed1ng v\u00ed d\u1ee5 trong <em>zone-sample.\r\n        \r\n      \r\n      <div class=\"cz-main\">\r\n        <div class=\"cz-actions\">\r\n          <label class=\"cz-file\">\r\n            <div>\ud83d\udcc1 Ch\u1ecdn file (.csv, .xlsx, .txt)\r\n            <input id=\"cz-file-input\" type=\"file\" accept=\".csv,.xlsx,.xls,.txt,.zone\" \/>\r\n          \r\n          <button id=\"cz-convert\" class=\"cz-btn cz-btn-primary\" disabled>\ud83d\ude80 Chuy\u1ec3n \u0110\u1ed5i\r\n          <button id=\"cz-clear\" class=\"cz-btn cz-btn-danger\">\ud83d\uddd1\ufe0f X\u00f3a\r\n        \r\n        \r\n        <div class=\"cz-row\">\r\n          <div class=\"cz-toggle\" id=\"cz-input-mode\">\r\n            <button class=\"cz-toggle-btn active\" data-mode=\"file\">T\u1ec7p\r\n            <button class=\"cz-toggle-btn\" data-mode=\"paste\">D\u00e1n\r\n          \r\n          <div class=\"cz-toggle\" id=\"cz-paste-type\" style=\"display:none\">\r\n            <button class=\"cz-toggle-btn active\" data-ptype=\"zone\">Zone (.txt)\r\n            <button class=\"cz-toggle-btn\" data-ptype=\"csv\">CSV\r\n          \r\n        \r\n\r\n        <div class=\"cz-row\" id=\"cz-paste-wrap\" style=\"display:none\">\r\n          <textarea id=\"cz-paste\" rows=\"8\" style=\"width:100%; padding:12px; border:2px solid #e5e7eb; border-radius:12px; background:#fafbfc; color:#374151; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size:12px; resize:vertical;\">\r\n        \r\n        <div class=\"cz-statusbar\">\r\n          <div id=\"cz-fileinfo\" class=\"cz-badge\">Ch\u01b0a ch\u1ecdn file\r\n          <div class=\"cz-toggle\" id=\"cz-view-mode\">\r\n            <button class=\"cz-toggle-btn active\" data-view=\"zone\">Hi\u1ec3n th\u1ecb Zone\r\n            <button class=\"cz-toggle-btn\" data-view=\"csv\">Hi\u1ec3n th\u1ecb CSV\r\n          \r\n          <button id=\"cz-copy\" class=\"cz-btn cz-btn-secondary\" disabled>\ud83d\udccb Sao ch\u00e9p\r\n          <button id=\"cz-download-zone\" class=\"cz-btn cz-btn-secondary\" disabled>\ud83d\udcbe T\u1ea3i .txt\r\n          <button id=\"cz-download-csv\" class=\"cz-btn cz-btn-secondary\" disabled>\ud83d\udcbe T\u1ea3i .csv\r\n          <button id=\"cz-download-xlsx\" class=\"cz-btn cz-btn-secondary\" disabled>\ud83d\udcbe T\u1ea3i .xlsx\r\n          <div id=\"cz-status\" class=\"cz-status\">\r\n        \r\n        <div id=\"cz-error\" class=\"cz-error\"><div>\u26a0\ufe0f<div id=\"cz-error-text\">\r\n        <div id=\"cz-output\" class=\"cz-output\">\r\n          <div class=\"cz-empty\">S\u1eb5n s\u00e0ng chuy\u1ec3n \u0111\u1ed5i... T\u1ea3i t\u1ec7p ho\u1eb7c d\u00e1n n\u1ed9i dung (CSV\/XLSX\/Zone) \u0111\u1ec3 b\u1eaft \u0111\u1ea7u.\r\n        \r\n      \r\n      <div class=\"cz-footer\">\r\n        <div class=\"cz-footer-content\">\r\n          <span>Ph\u00e1t tri\u1ec3n b\u1edfi\r\n          <strong class=\"cz-footer-brand\">DPS.MEDIA\r\n          <span>\u2022\r\n          <span>C\u00f4ng c\u1ee5 s\u1ed1 chuy\u00ean nghi\u1ec7p\r\n          <span>\u2022\r\n          <a class=\"cz-footer-link\" href=\"https:\/\/dps.media\" target=\"_blank\">dps.media\r\n        \r\n      \r\n    \r\n  `;\r\n\r\n  \/\/ Elements\r\n  const fileInput = root.querySelector('#cz-file-input');\r\n  const btnConvert = root.querySelector('#cz-convert');\r\n  const btnClear = root.querySelector('#cz-clear');\r\n  const btnCopy = root.querySelector('#cz-copy');\r\n  const btnDownloadZone = root.querySelector('#cz-download-zone');\r\n  const btnDownloadCSV = root.querySelector('#cz-download-csv');\r\n  const btnDownloadXLSX = root.querySelector('#cz-download-xlsx');\r\n  const outEl = root.querySelector('#cz-output');\r\n  const statusEl = root.querySelector('#cz-status');\r\n  const fileInfoEl = root.querySelector('#cz-fileinfo');\r\n  const errorEl = root.querySelector('#cz-error');\r\n  const errorTextEl = root.querySelector('#cz-error-text');\r\n  const inputModeWrap = root.querySelector('#cz-input-mode');\r\n  const pasteTypeWrap = root.querySelector('#cz-paste-type');\r\n  const pasteWrap = root.querySelector('#cz-paste-wrap');\r\n  const pasteTextarea = root.querySelector('#cz-paste');\r\n  const viewModeWrap = root.querySelector('#cz-view-mode');\r\n\r\n  let lastZoneText = '';\r\n  let lastCSVText = '';\r\n  let currentFile = null;\r\n  let inputMode = 'file'; \/\/ 'file' | 'paste'\r\n  let pasteType = 'zone'; \/\/ 'zone' | 'csv'\r\n  let viewMode = 'zone'; \/\/ 'zone' | 'csv'\r\n\r\n  function setStatus(msg){ statusEl.textContent = msg || ''; }\r\n  function showError(msg){ errorEl.style.display = 'flex'; errorTextEl.textContent = msg; }\r\n  function clearError(){ errorEl.style.display = 'none'; errorTextEl.textContent = ''; }\r\n  function setOutputText(text){ outEl.textContent = text || ''; if(!text){ outEl.innerHTML = '<div class=\"cz-empty\">Kh\u00f4ng c\u00f3 d\u1eef li\u1ec7u...'; } }\r\n  function refreshOutputView(){\r\n    if (viewMode === 'zone') {\r\n      setOutputText(lastZoneText);\r\n    } else {\r\n      setOutputText(lastCSVText);\r\n    }\r\n    btnCopy.disabled = !(viewMode === 'zone' ? lastZoneText : lastCSVText);\r\n    btnDownloadZone.disabled = !lastZoneText;\r\n    btnDownloadCSV.disabled = !lastCSVText;\r\n    btnDownloadXLSX.disabled = !lastCSVText;\r\n  }\r\n\r\n  \/\/ Lightweight CSV parser (handles quotes, commas, newlines)\r\n  function parseCSV(text){\r\n    const rows = [];\r\n    let i = 0, field = '', row = [], inQuotes = false; \r\n    while (i < text.length){\r\n      const c = text[i];\r\n      if (inQuotes){\r\n        if (c === '\"'){\r\n          if (i + 1 < text.length && text[i+1] === '\"'){ field += '\"'; i += 2; continue; }\r\n          inQuotes = false; i++; continue;\r\n        } else { field += c; i++; continue; }\r\n      } else {\r\n        if (c === '\"'){ inQuotes = true; i++; continue; }\r\n        if (c === ','){ row.push(field); field = ''; i++; continue; }\r\n        if (c === '\\n'){ row.push(field); rows.push(row); field=''; row=[]; i++; continue; }\r\n        if (c === '\\r'){ \/\/ handle CRLF\r\n          if (i + 1 < text.length && text[i+1] === '\\n'){ i += 2; row.push(field); rows.push(row); field=''; row=[]; continue; }\r\n          i++; continue;\r\n        }\r\n        field += c; i++; continue;\r\n      }\r\n    }\r\n    \/\/ last field\r\n    if (field.length || row.length){ row.push(field); rows.push(row); }\r\n    return rows;\r\n  }\r\n\r\n  \/\/ Utilities\r\n  function toHeadersMap(arr){\r\n    const map = {};\r\n    arr.forEach((h, idx) => { if (!h) return; map[String(h).trim().toLowerCase()] = idx; });\r\n    return map;\r\n  }\r\n  function safeGet(row, idx){ return (idx != null && idx >= 0 && idx < row.length) ? String(row[idx]).trim() : ''; }\r\n  function normalizeTXT(val){\r\n    if (val == null) return '\"\"';\r\n    let s = String(val).trim();\r\n    if (!s.length) return '\"\"';\r\n    \/\/ If already wrapped in quotes, return as-is (normalize to double quotes)\r\n    const starts = s.startsWith('\"') || s.startsWith(\"'\");\r\n    const ends = s.endsWith('\"') || s.endsWith(\"'\");\r\n    if (starts && ends && s.length >= 2){\r\n      \/\/ normalize outer to double quotes\r\n      s = '\"' + s.slice(1, -1) + '\"';\r\n      return s;\r\n    }\r\n    \/\/ Escape embedded quotes by doubling\r\n    s = s.replace(\/\"\/g, '\"\"');\r\n    return '\"' + s + '\"';\r\n  }\r\n  function normalizeHost(h){\r\n    return String(h || '').trim();\r\n  }\r\n  function normalizeType(t){ return String(t || '').trim().toUpperCase(); }\r\n  function toInt(v, def){ const n = parseInt(String(v||'').trim(), 10); return Number.isFinite(n) ? n : def; }\r\n  function isNumberToken(tok){ return \/^\\d+$\/.test(String(tok||'').trim()); }\r\n  function extractMX(data){\r\n    const parts = String(data||'').trim().split(\/\\s+\/);\r\n    if (parts.length >= 2 && isNumberToken(parts[0])){\r\n      return { Priority: parts[0], Data: parts.slice(1).join(' ') };\r\n    }\r\n    return { Priority: '', Data: String(data||'').trim() };\r\n  }\r\n\r\n  function splitMultiDataIfNeeded(type, data){\r\n    if (type === 'NS'){\r\n      \/\/ Support comma-separated NS targets\r\n      return String(data||'').split(',').map(s => s.trim()).filter(Boolean);\r\n    }\r\n    return [String(data||'').trim()];\r\n  }\r\n\r\n  \/\/ Build zone lines from structured rows\r\n  function buildZoneLines(rows){\r\n    const lines = [];\r\n    for (const r of rows){\r\n      const host = normalizeHost(r.Host ?? r.host ?? r.HOST ?? r[\"Host\"]);\r\n      const type = normalizeType(r.Type ?? r.type ?? r.TYPE ?? r[\"Type\"]);\r\n      const ttl = toInt(r.TTL ?? r.ttl ?? r.Ttl ?? r[\"TTL\"], 3600);\r\n      let data = r.Data ?? r.data ?? r.DATA ?? r[\"Data\"];\r\n\r\n      \/\/ MX may have Priority column\r\n      if (type === 'MX' && (r.Priority != null || r.priority != null)){\r\n        const prio = String(r.Priority ?? r.priority).trim();\r\n        if (prio) data = prio + ' ' + String(data ?? '').trim();\r\n      }\r\n\r\n      if (!host || !type){ continue; }\r\n\r\n      if (type === 'TXT'){\r\n        const txtVal = normalizeTXT(String(data ?? ''));\r\n        lines.push(`${host}\\t${ttl}\\tIN\\tTXT\\t${txtVal}`);\r\n        continue;\r\n      }\r\n\r\n      const parts = splitMultiDataIfNeeded(type, data);\r\n      for (const p of parts){\r\n        if (!p) continue;\r\n        lines.push(`${host}\\t${ttl}\\tIN\\t${type}\\t${p}`);\r\n      }\r\n    }\r\n    return lines;\r\n  }\r\n\r\n  \/\/ Parse Cloudflare zone txt (line-based) \u2192 records\r\n  function parseZoneToRecords(text){\r\n    const lines = String(text||'').split(\/\\r?\\n\/);\r\n    const out = [];\r\n    for (let raw of lines){\r\n      let line = String(raw||'').trim();\r\n      if (!line || line.startsWith(';') || line.startsWith('#')) continue;\r\n      const parts = line.split(\/\\s+\/);\r\n      if (parts.length < 3) continue;\r\n      let i = 0;\r\n      const host = parts[i++];\r\n      let ttl = 3600;\r\n      if (parts[i] && isNumberToken(parts[i])){ ttl = parseInt(parts[i++], 10); }\r\n      if (parts[i] && \/^IN$\/i.test(parts[i])){ i++; }\r\n      const type = (parts[i++]||'').toUpperCase();\r\n      if (!type) continue;\r\n      const data = parts.slice(i).join(' ');\r\n      const rec = { Host: host, Type: type, Data: data, TTL: ttl };\r\n      if (type === 'MX'){\r\n        const mx = extractMX(data);\r\n        rec.Data = mx.Data; rec.Priority = mx.Priority;\r\n      }\r\n      out.push(rec);\r\n    }\r\n    return out;\r\n  }\r\n\r\n  function recordsToCSVText(records){\r\n    const normalized = records.map(r => ({\r\n      Host: normalizeHost(r.Host ?? r.host),\r\n      Type: normalizeType(r.Type ?? r.type),\r\n      Data: String(r.Data ?? r.data ?? ''),\r\n      TTL: toInt(r.TTL ?? r.ttl, 3600),\r\n      Priority: r.Priority ?? r.priority ?? ''\r\n    }));\r\n    const hasPriority = normalized.some(r => String(r.Priority||'').trim());\r\n    const header = hasPriority ? ['Host','Type','Data','TTL','Priority'] : ['Host','Type','Data','TTL'];\r\n    const escapeCSV = (v)=>{\r\n      const s = String(v ?? '');\r\n      if (\/[\",\\n]\/.test(s)) return '\"' + s.replace(\/\"\/g,'\"\"') + '\"';\r\n      return s;\r\n    };\r\n    const lines = [header.join(',')];\r\n    for (const r of normalized){\r\n      const arr = hasPriority ? [r.Host, r.Type, r.Data, r.TTL, r.Priority] : [r.Host, r.Type, r.Data, r.TTL];\r\n      lines.push(arr.map(escapeCSV).join(','));\r\n    }\r\n    return lines.join('\\n');\r\n  }\r\n\r\n  function replaceTabsOutsideQuotes(s){\r\n    let out = '';\r\n    let inQuotes = false;\r\n    for (let i=0;i<s.length;i++){\r\n      const c = s[i];\r\n      if (c === '\"'){\r\n        \/\/ double quotes inside quotes\r\n        if (inQuotes && i+1 < s.length && s[i+1] === '\"'){ out += '\"\"'; i++; continue; }\r\n        inQuotes = !inQuotes; out += '\"'; continue;\r\n      }\r\n      if (!inQuotes && c === '\\t'){ out += ','; continue; }\r\n      out += c;\r\n    }\r\n    return out;\r\n  }\r\n\r\n  \/\/ Parse CSV\/TSV text to array of objects using header row\r\n  function csvTextToObjects(text){\r\n    let body = String(text||'');\r\n    \/\/ If looks like TSV, normalize to CSV\r\n    const tabCount = (body.match(\/\\t\/g)||[]).length;\r\n    const commaCount = (body.match(\/,\/g)||[]).length;\r\n    if (tabCount > commaCount) body = replaceTabsOutsideQuotes(body);\r\n    const table = parseCSV(body);\r\n    if (!table.length) return [];\r\n    const header = table[0];\r\n    const hmap = toHeadersMap(header);\r\n    const out = [];\r\n    for (let i=1; i<table.length; i++){\r\n      const row = table[i];\r\n      if (!row || row.length === 0) continue;\r\n      const obj = {};\r\n      for (const [key, idx] of Object.entries(hmap)){\r\n        obj[key] = safeGet(row, idx);\r\n      }\r\n      \/\/ Normalize keys to canonical casing\r\n      const record = {\r\n        Host: obj['host'] ?? '',\r\n        Type: obj['type'] ?? '',\r\n        Data: obj['data'] ?? '',\r\n        TTL: obj['ttl'] ?? obj['time to live'] ?? ''\r\n      };\r\n      \/\/ Optional Priority\r\n      if (obj['priority'] != null) record.Priority = obj['priority'];\r\n      out.push(record);\r\n    }\r\n    return out;\r\n  }\r\n\r\n  \/\/ XLSX loader (from CDN) \u2014 only if needed\r\n  let xlsxReady = false;\r\n  async function ensureXLSX(){\r\n    if (xlsxReady || (typeof window !== 'undefined' && window.XLSX)) { xlsxReady = true; return; }\r\n    setStatus('\u0110ang t\u1ea3i th\u01b0 vi\u1ec7n XLSX...');\r\n    await new Promise((resolve, reject) => {\r\n      const s = document.createElement('script');\r\n      s.src = 'https:\/\/cdn.jsdelivr.net\/npm\/xlsx@0.18.5\/dist\/xlsx.full.min.js';\r\n      s.onload = () => { xlsxReady = true; resolve(); };\r\n      s.onerror = () => reject(new Error('Kh\u00f4ng t\u1ea3i \u0111\u01b0\u1ee3c th\u01b0 vi\u1ec7n XLSX. Vui l\u00f2ng d\u00f9ng CSV.'));\r\n      document.head.appendChild(s);\r\n    });\r\n  }\r\n\r\n  async function fileToRows(file){\r\n    const name = (file?.name || '').toLowerCase();\r\n    if (name.endsWith('.csv')){\r\n      const text = await file.text();\r\n      return csvTextToObjects(text);\r\n    }\r\n    if (name.endsWith('.xlsx') || name.endsWith('.xls')){\r\n      await ensureXLSX();\r\n      const ab = await file.arrayBuffer();\r\n      const wb = window.XLSX.read(ab, { type: 'array' });\r\n      const sheetName = wb.SheetNames[0];\r\n      const ws = wb.Sheets[sheetName];\r\n      \/\/ to JSON with header row detection\r\n      const arr = window.XLSX.utils.sheet_to_json(ws, { defval: '' });\r\n      \/\/ Attempt to map any casing\r\n      return arr.map(r => ({\r\n        Host: r.Host ?? r.host ?? r.HOST ?? r[\"Host\"] ?? r[\"HOST\"] ?? '',\r\n        Type: r.Type ?? r.type ?? r.TYPE ?? r[\"Type\"] ?? r[\"TYPE\"] ?? '',\r\n        Data: r.Data ?? r.data ?? r.DATA ?? r[\"Data\"] ?? r[\"DATA\"] ?? '',\r\n        TTL: r.TTL ?? r.ttl ?? r.Ttl ?? r[\"TTL\"] ?? r[\"Ttl\"] ?? '',\r\n        Priority: r.Priority ?? r.priority ?? r.PRIORITY\r\n      }));\r\n    }\r\n    if (name.endsWith('.txt') || name.endsWith('.zone')){\r\n      const text = await file.text();\r\n      return parseZoneToRecords(text);\r\n    }\r\n    throw new Error('\u0110\u1ecbnh d\u1ea1ng kh\u00f4ng h\u1ed7 tr\u1ee3. H\u00e3y ch\u1ecdn .csv ho\u1eb7c .xlsx');\r\n  }\r\n\r\n  function deriveDownloadName(file){\r\n    if (!file || !file.name) return 'cloudflare-zone.txt';\r\n    const base = file.name.replace(\/\\.[^.]+$\/,'');\r\n    const pre = base.includes('-') ? base.split('-')[0] : base;\r\n    return pre + '-cloudflare-bind-fixed.txt';\r\n  }\r\n\r\n  async function convertCurrent(){\r\n    clearError(); setStatus('\u0110ang x\u1eed l\u00fd...');\r\n    try {\r\n      let records = [];\r\n      if (inputMode === 'file'){\r\n        if (!currentFile){ showError('Vui l\u00f2ng ch\u1ecdn t\u1ec7p CSV\/XLSX\/TXT.'); setStatus(''); return; }\r\n        records = await fileToRows(currentFile);\r\n      } else {\r\n        const text = pasteTextarea.value || '';\r\n        if (!text.trim()){ showError('Vui l\u00f2ng d\u00e1n n\u1ed9i dung \u0111\u1ec3 chuy\u1ec3n \u0111\u1ed5i.'); setStatus(''); return; }\r\n        if (pasteType === 'csv') records = csvTextToObjects(text);\r\n        else records = parseZoneToRecords(text);\r\n      }\r\n\r\n      if (!records.length){ lastZoneText=''; lastCSVText=''; refreshOutputView(); setStatus('Kh\u00f4ng t\u00ecm th\u1ea5y d\u1eef li\u1ec7u.'); return; }\r\n\r\n      const zoneLines = buildZoneLines(records);\r\n      lastZoneText = zoneLines.join('\\n');\r\n      lastCSVText = recordsToCSVText(records);\r\n      refreshOutputView();\r\n      setStatus(`\u2705 Ho\u00e0n th\u00e0nh: ${records.length} b\u1ea3n ghi`);\r\n    } catch(e){\r\n      showError(e.message || String(e)); setStatus('\u274c L\u1ed7i');\r\n    }\r\n  }\r\n\r\n  \/\/ Events\r\n  fileInput.addEventListener('change', () => {\r\n    clearError();\r\n    const f = fileInput.files && fileInput.files[0];\r\n    currentFile = f || null;\r\n    if (currentFile){\r\n      fileInfoEl.textContent = `\u0110\u00e3 ch\u1ecdn: ${currentFile.name}`;\r\n      btnConvert.disabled = false;\r\n    } else {\r\n      fileInfoEl.textContent = 'Ch\u01b0a ch\u1ecdn file';\r\n      btnConvert.disabled = true;\r\n      btnCopy.disabled = true; btnDownloadZone.disabled = true; btnDownloadCSV.disabled = true; btnDownloadXLSX.disabled = true;\r\n      lastZoneText = ''; lastCSVText=''; setOutputText('');\r\n    }\r\n    setStatus('');\r\n  });\r\n\r\n  btnConvert.addEventListener('click', convertCurrent);\r\n\r\n  btnClear.addEventListener('click', () => {\r\n    fileInput.value = '';\r\n    currentFile = null;\r\n    lastZoneText = '';\r\n    lastCSVText = '';\r\n    btnConvert.disabled = true;\r\n    btnCopy.disabled = true;\r\n    btnDownloadZone.disabled = true;\r\n    btnDownloadCSV.disabled = true;\r\n    btnDownloadXLSX.disabled = true;\r\n    fileInfoEl.textContent = 'Ch\u01b0a ch\u1ecdn file';\r\n    setOutputText('');\r\n    clearError();\r\n    setStatus('\u0110\u00e3 x\u00f3a.');\r\n  });\r\n\r\n  async function copyToClipboard(text){\r\n    if (!text) return;\r\n    if (navigator.clipboard && navigator.clipboard.writeText){ await navigator.clipboard.writeText(text); return; }\r\n    const ta = document.createElement('textarea');\r\n    ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta);\r\n  }\r\n\r\n  btnCopy.addEventListener('click', async () => {\r\n    const payload = (viewMode === 'zone') ? lastZoneText : lastCSVText;\r\n    if (!payload){ setStatus('\u274c Kh\u00f4ng c\u00f3 d\u1eef li\u1ec7u \u0111\u1ec3 sao ch\u00e9p'); return; }\r\n    try { await copyToClipboard(payload); setStatus('\u2705 \u0110\u00e3 sao ch\u00e9p v\u00e0o clipboard'); } catch(e){ setStatus('\u274c Sao ch\u00e9p th\u1ea5t b\u1ea1i'); }\r\n  });\r\n\r\n  btnDownloadZone.addEventListener('click', () => {\r\n    if (!lastZoneText){ setStatus('\u274c Kh\u00f4ng c\u00f3 d\u1eef li\u1ec7u \u0111\u1ec3 t\u1ea3i'); return; }\r\n    const blob = new Blob([lastZoneText], { type: 'text\/plain;charset=utf-8' });\r\n    const url = URL.createObjectURL(blob);\r\n    const a = document.createElement('a');\r\n    a.href = url; a.download = deriveDownloadName(currentFile);\r\n    document.body.appendChild(a); a.click(); document.body.removeChild(a);\r\n    URL.revokeObjectURL(url);\r\n    setStatus('\ud83d\udcbe \u0110\u00e3 t\u1ea3i zone.txt');\r\n  });\r\n\r\n  btnDownloadCSV.addEventListener('click', () => {\r\n    if (!lastCSVText){ setStatus('\u274c Kh\u00f4ng c\u00f3 d\u1eef li\u1ec7u \u0111\u1ec3 t\u1ea3i'); return; }\r\n    const name = (currentFile?.name || 'cloudflare-zone').replace(\/\\.[^.]+$\/,'') + '.csv';\r\n    const blob = new Blob([lastCSVText], { type: 'text\/csv;charset=utf-8' });\r\n    const url = URL.createObjectURL(blob);\r\n    const a = document.createElement('a');\r\n    a.href = url; a.download = name;\r\n    document.body.appendChild(a); a.click(); document.body.removeChild(a);\r\n    URL.revokeObjectURL(url);\r\n    setStatus('\ud83d\udcbe \u0110\u00e3 t\u1ea3i CSV');\r\n  });\r\n\r\n  btnDownloadXLSX.addEventListener('click', async () => {\r\n    try{\r\n      await ensureXLSX();\r\n      const records = csvTextToObjects(lastCSVText);\r\n      const ws = window.XLSX.utils.json_to_sheet(records);\r\n      const wb = window.XLSX.utils.book_new();\r\n      window.XLSX.utils.book_append_sheet(wb, ws, 'DNS');\r\n      const name = (currentFile?.name || 'cloudflare-zone').replace(\/\\.[^.]+$\/,'') + '.xlsx';\r\n      window.XLSX.writeFile(wb, name);\r\n      setStatus('\ud83d\udcbe \u0110\u00e3 t\u1ea3i XLSX');\r\n    }catch(e){ setStatus('\u274c L\u1ed7i xu\u1ea5t XLSX'); showError(e.message || String(e)); }\r\n  });\r\n\r\n  \/\/ Input mode toggles\r\n  function updateInputMode(){\r\n    const fileActive = inputMode === 'file';\r\n    pasteWrap.style.display = fileActive ? 'none' : 'flex';\r\n    pasteTypeWrap.style.display = fileActive ? 'none' : 'inline-flex';\r\n    fileInfoEl.textContent = fileActive ? (currentFile ? `\u0110\u00e3 ch\u1ecdn: ${currentFile.name}` : 'Ch\u01b0a ch\u1ecdn file') : (pasteType === 'zone' ? 'D\u00e1n: Zone' : 'D\u00e1n: CSV');\r\n    if (fileActive){\r\n      btnConvert.disabled = !currentFile;\r\n    } else {\r\n      btnConvert.disabled = !(pasteTextarea.value && pasteTextarea.value.trim());\r\n    }\r\n  }\r\n  inputModeWrap.querySelectorAll('.cz-toggle-btn').forEach(btn => {\r\n    btn.addEventListener('click', ()=>{\r\n      inputModeWrap.querySelectorAll('.cz-toggle-btn').forEach(b=>b.classList.remove('active'));\r\n      btn.classList.add('active');\r\n      inputMode = btn.getAttribute('data-mode');\r\n      updateInputMode();\r\n    });\r\n  });\r\n  pasteTypeWrap.querySelectorAll('.cz-toggle-btn').forEach(btn => {\r\n    btn.addEventListener('click', ()=>{\r\n      pasteTypeWrap.querySelectorAll('.cz-toggle-btn').forEach(b=>b.classList.remove('active'));\r\n      btn.classList.add('active');\r\n      pasteType = btn.getAttribute('data-ptype');\r\n      updateInputMode();\r\n    });\r\n  });\r\n\r\n  pasteTextarea.addEventListener('input', ()=>{\r\n    if (inputMode === 'paste'){\r\n      btnConvert.disabled = !(pasteTextarea.value && pasteTextarea.value.trim());\r\n    }\r\n  });\r\n\r\n  \/\/ View mode toggles\r\n  function updateViewMode(){\r\n    viewModeWrap.querySelectorAll('.cz-toggle-btn').forEach(b=>b.classList.remove('active'));\r\n    const activeBtn = viewModeWrap.querySelector(`.cz-toggle-btn[data-view=\"${viewMode}\"]`);\r\n    if (activeBtn) activeBtn.classList.add('active');\r\n    refreshOutputView();\r\n  }\r\n  viewModeWrap.querySelectorAll('.cz-toggle-btn').forEach(btn => {\r\n    btn.addEventListener('click', ()=>{\r\n      viewMode = btn.getAttribute('data-view');\r\n      updateViewMode();\r\n    });\r\n  });\r\n\r\n  updateInputMode();\r\n  updateViewMode();\r\n})();\r\n<\/script><!-- End CSV\/XLSX \u2192 Cloudflare Zone Converter --><p><strong>Introduction<\/strong><\/p><ul class=\"wp-block-list\">\n<li>CSV\/XLSX \u2192 Cloudflare Zone Converter helps you convert DNS records list (CSV\/XLSX or Zone .txt) to standard BIND zone file for import into Cloudflare.<\/li>\n\n\n\n<li>Cloudflare has a \u201cscan\u201d DNS feature when adding a domain, but it often misses SRV, CAA, TXT (DKIM\/DMARC\/Verify), specific MX. The most reliable way is to export DNS data from the current provider, convert via this tool, then import into Cloudflare.<\/li>\n<\/ul><p><strong>When to Use<\/strong><\/p><ul class=\"wp-block-list\">\n<li>Migrate DNS from provider A to Cloudflare.<\/li>\n\n\n\n<li>Want to sync all advanced records (long TXT, SRV, CAA, NS\u2026).<\/li>\n\n\n\n<li>Need to export\/import in Zone (.txt) format, or standardize CSV\/XLSX before import.<\/li>\n<\/ul><p><strong>Prepare Data (From Provider)<\/strong><br>You need to get all DNS records. There are 3 common scenarios in VN:<\/p><ul class=\"wp-block-list\">\n<li>Using \u201cstandard panel\u201d:\n<ul class=\"wp-block-list\">\n<li>&gt;cPanel: go to \u201cZone Editor\u201d &gt; \u201cManage\u201d to view all records. cPanel doesn't always have an export button. You can:\n<ul class=\"wp-block-list\">\n<li>Request zone file from provider (ticket).<\/li>\n\n\n\n<li>Or copy table to Excel then \u201cSave as CSV\u201d by columns:\u00a0Host,Type,Data,TTL\u00a0(MX can add\u00a0Priority).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>DirectAdmin: \u201cDNS Management\u201d sometimes has \u201cExport as Bind file\u201d (depending on config). If available, download\u00a0.txt\/.zone. If not, copy table to CSV.<\/li>\n\n\n\n<li>Plesk: \u201cWebsites & Domains\u201d > \u201cDNS Settings\u201d > \u201cExport DNS zone file\u201d (n\u1ebfu b\u1eadt). T\u1ea3i\u00a0.txt. N\u1ebfu kh\u00f4ng c\u00f3, copy b\u1ea3ng sang CSV.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Using brand-specific (often customized panel): M\u1eaft B\u00e3o, PA Vi\u1ec7t Nam, Nh\u00e2n H\u00f2a, Tenten, iNET, Z.com, VinaHost, AZDIGI, BKNS, DIGISTAR\u2026\n<ul class=\"wp-block-list\">\n<li>Find \u201cDNS\u201d, \u201cDNS Management\u201d or \u201cZone\/Records\u201d section.<\/li>\n\n\n\n<li>If has \u201cExport\/Backup DNS\u201d, download\u00a0.txt\u00a0or\u00a0.csv.<\/li>\n\n\n\n<li>If not, open table, select all copy to Excel\/Google Sheets, check columns then \u201cSave as CSV\u201d.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>If unavoidable:\n<ul class=\"wp-block-list\">\n<li>Contact support to request zone file (BIND). This is the fastest and least error-prone way.<\/li>\n<\/ul>\n<\/li>\n<\/ul><p>CSV Input Tips (tool accepts):<\/p><ul class=\"wp-block-list\">\n<li>Column headers:\u00a0Host,Type,Data,TTL\u00a0(case insensitive).\u00a0Priority\u00a0is optional column for MX.<\/li>\n\n\n\n<li>TXT: Data c\u00f3 th\u1ec3 c\u00f3 d\u1ea5u ngo\u1eb7c k\u00e9p; tool s\u1ebd t\u1ef1 chu\u1ea9n h\u00f3a\/\u0111\u1eb7t quote. V\u00ed d\u1ee5:\u00a0\u201cv=spf1 include:mail\u2026 ~all\u201d.<\/li>\n\n\n\n<li>MX: 2 valid ways:\n<ul class=\"wp-block-list\">\n<li>Has\u00a0Priority\u00a0column and\u00a0Data = mail.domain.com.; or<\/li>\n\n\n\n<li>No Priority column and\u00a0Data = 10 mail.domain.com.\u00a0(tool auto splits).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>SRV: put full \u201cpriority weight port target\u201d in\u00a0Data, e.g.\u00a00 0 443 mail.domain.com..<\/li>\n\n\n\n<li>NS: if Data has multiple nameservers separated by commas, tool will split into multiple zone lines.<\/li>\n\n\n\n<li>TTL: if empty, tool defaults to\u00a03600.<\/li>\n<\/ul><p><strong>Use Tool (csvtozoneconverter.html)<\/strong><\/p><ul class=\"wp-block-list\">\n<li>Input data:\n<ul class=\"wp-block-list\">\n<li>Select \u201cFile\u201d and upload\u00a0.csv,\u00a0.xlsx\/.xls,\u00a0.txt\/.zone; or<\/li>\n\n\n\n<li>Select \u201cPaste\u201d and choose type \u201cCSV\u201d or \u201cZone (.txt)\u201d to paste content directly.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Click \u201cConvert\u201d.<\/li>\n\n\n\n<li>View results:\n<ul class=\"wp-block-list\">\n<li>\u201cShow Zone\u201d to view BIND zone (standard for Cloudflare import).<\/li>\n\n\n\n<li>\u201cShow CSV\u201d if you want to review in table format.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Export results:\n<ul class=\"wp-block-list\">\n<li>\u201cCopy\u201d to copy in current display mode.<\/li>\n\n\n\n<li>\u201cDownload .txt\u201d to get zone file (named like Cloudflare e.g.\u00a0domain-cloudflare-bind-fixed.txt).<\/li>\n\n\n\n<li>\u201cDownload .csv\/.xlsx\u201d if you want to save in table format.<\/li>\n<\/ul>\n<\/li>\n<\/ul><p><strong>Upload to Cloudflare<\/strong><\/p><ul class=\"wp-block-list\">\n<li>Add domain to Cloudflare (Add a site).<\/li>\n\n\n\n<li>Go to DNS &gt; Advanced Actions &gt; Import and Export DNS &gt; \u201cImport\u201d.<\/li>\n\n\n\n<li>Upload the\u00a0.txt\u00a0(zone BIND) file that you exported from the tool.<\/li>\n\n\n\n<li>Check again:\n<ul class=\"wp-block-list\">\n<li>Full MX, TXT (DKIM\/DMARC\/Google verify), SRV, CAA, internal NS (if any), PTR (if used) records.<\/li>\n\n\n\n<li>Turn on\/off \u201cProxy\u201d (orange cloud) as needed for A\/CNAME web records. Mail, FTP, SIP\u2026 records are usually set to \u201cDNS only\u201d.<\/li>\n\n\n\n<li>TTL: Cloudflare may change some TTLs to \u201cAuto\u201d. This is normal.<\/li>\n\n\n\n<li>DNSSEC: reconfigure DNSSEC in Cloudflare (does not move with zone file).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Point the domain's nameserver to Cloudflare's 2 NS records as instructed.<\/li>\n<\/ul><p><strong>Why Not Just Use \u201cScan\u201d<\/strong><\/p><ul class=\"wp-block-list\">\n<li>Cloudflare's Scan misses SRV\/CAA\/long TXT and other internal records.<\/li>\n\n\n\n<li>Importing from standardized zone\/CSV file preserves all records, avoids email\/DKIM\/app disruptions.<\/li>\n<\/ul><p><strong>Supported Record Types<\/strong><\/p><ul class=\"wp-block-list\">\n<li>Supports all common records: A, AAAA, CNAME, MX, NS, TXT, SRV, CAA, PTR\u2026<\/li>\n\n\n\n<li>Special handling:\n<ul class=\"wp-block-list\">\n<li>TXT always properly quoted per standard.<\/li>\n\n\n\n<li>MX intelligently splits Priority.<\/li>\n\n\n\n<li>Multi-target NS (comma-separated) \u2192 multiple zone lines.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>If the record is unusual or has a special format, paste it in Zone format into the tool for safe conversion.<\/li>\n<\/ul><p><strong>M\u1eb9o & L\u01b0u \u00dd<\/strong><\/p><ul class=\"wp-block-list\">\n<li>Ensure hostname\/target ends with a dot (.) when it's FQDN (e.g.\u00a0mail.domain.com.). Cloudflare accepts without the dot, but standard BIND usually has it.<\/li>\n\n\n\n<li>After import, quick test:\u00a0A,\u00a0CNAME\u00a0website;\u00a0MX\/TXT\u00a0email;\u00a0SRV\u00a0service;\u00a0CAA\u00a0certificate.<\/li>\n\n\n\n<li>If XLSX cannot be loaded (CDN blocked), use CSV.<\/li>\n<\/ul><p>Do you need me to add detailed instructions for the specific panel you're using (e.g. screenshots step by step for M\u1eaft B\u00e3o\/PA\/DirectAdmin\/Plesk)?<\/p>","protected":false},"excerpt":{"rendered":"<p>Gi\u1edbi Thi\u1ec7u Khi N\u00e0o D\u00f9ng Chu\u1ea9n B\u1ecb D\u1eef Li\u1ec7u (T\u1eeb Nh\u00e0 Cung C\u1ea5p)B\u1ea1n c\u1ea7n l\u1ea5y to\u00e0n b\u1ed9 b\u1ea3n ghi DNS. C\u00f3 3 t\u00ecnh hu\u1ed1ng ph\u1ed5 bi\u1ebfn \u1edf VN: M\u1eb9o nh\u1eadp li\u1ec7u CSV (tool ch\u1ea5p nh\u1eadn): D\u00f9ng C\u00f4ng C\u1ee5 (csvtozoneconverter.html) Nh\u1eadp L\u00ean Cloudflare V\u00ec Sao Kh\u00f4ng N\u00ean Ch\u1ec9 D\u00f9ng \u201cScan\u201d C\u00e1c Lo\u1ea1i B\u1ea3n Ghi H\u1ed7 Tr\u1ee3 [&hellip;]<\/p>","protected":false},"author":1,"featured_media":32750,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-32748","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"rankmath_keywords":{"primary":"","secondary":[""]},"yoast_keywords":{"primary":"","secondary":[]},"yoast_focuskw":"","rankmath_focuskw":"","seo_keywords":{"primary":"","secondary":[""]},"_links":{"self":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/pages\/32748","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/comments?post=32748"}],"version-history":[{"count":1,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/pages\/32748\/revisions"}],"predecessor-version":[{"id":32752,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/pages\/32748\/revisions\/32752"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/media\/32750"}],"wp:attachment":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/media?parent=32748"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}