{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "dns-record-manager",
  "title": "Dns Record Manager",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/dns-record-manager/DnsRecordManager.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport {\n  Check,\n  Cloud,\n  CloudOff,\n  Copy,\n  Download,\n  Edit2,\n  FileCode,\n  Globe,\n  MoreHorizontal,\n  Plus,\n  Search,\n  ShieldAlert,\n  ShieldCheck,\n  Trash2,\n  X,\n} from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Input } from '@/components/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Separator } from '@/components/ui/separator'\nimport { Switch } from '@/components/ui/switch'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\n\nexport type DnsRecordType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'CAA'\n\nexport interface DnsRecord {\n  id: string\n  type: DnsRecordType\n  name: string\n  content: string\n  ttl: string\n  proxied: boolean\n  priority?: number\n  comment?: string\n}\n\ninterface Props {\n  domain?: string\n  nameservers?: string[]\n  dnssecEnabled?: boolean\n  initialRecords?: DnsRecord[]\n  initialAddOpen?: boolean\n  class?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  domain: 'uipkge.dev',\n  nameservers: () => ['ns1.uipkge.net', 'ns2.uipkge.net'],\n  dnssecEnabled: true,\n  initialRecords: undefined,\n  initialAddOpen: false,\n})\n\nconst defaultRecords: DnsRecord[] = [\n  {\n    id: 'rec-1',\n    type: 'A',\n    name: '@',\n    content: '76.76.21.21',\n    ttl: 'Auto',\n    proxied: true,\n    comment: 'Apex production load balancer',\n  },\n  {\n    id: 'rec-2',\n    type: 'CNAME',\n    name: 'www',\n    content: 'cname.vercel-dns.com',\n    ttl: 'Auto',\n    proxied: true,\n    comment: 'Primary web redirect',\n  },\n  {\n    id: 'rec-3',\n    type: 'CNAME',\n    name: 'api',\n    content: 'api-gateway.uipkge.net',\n    ttl: '300s',\n    proxied: false,\n    comment: 'Direct REST gateway route',\n  },\n  {\n    id: 'rec-4',\n    type: 'AAAA',\n    name: '@',\n    content: '2606:4700:3038::6815:1515',\n    ttl: 'Auto',\n    proxied: true,\n    comment: 'IPv6 edge ingress',\n  },\n  {\n    id: 'rec-5',\n    type: 'MX',\n    name: 'mail',\n    content: 'aspmx.l.google.com',\n    ttl: '3600s',\n    proxied: false,\n    priority: 10,\n    comment: 'Google Workspace MX',\n  },\n  {\n    id: 'rec-6',\n    type: 'TXT',\n    name: '_dmarc',\n    content: 'v=spf1 include:_spf.google.com ~all',\n    ttl: '3600s',\n    proxied: false,\n    comment: 'Sender Policy Framework verification',\n  },\n  {\n    id: 'rec-7',\n    type: 'CAA',\n    name: '@',\n    content: '0 issue \"letsencrypt.org\"',\n    ttl: 'Auto',\n    proxied: false,\n    comment: 'Certificate authority restriction',\n  },\n]\n\nconst records = ref<DnsRecord[]>(\n  props.initialRecords ? props.initialRecords.map((r) => ({ ...r })) : defaultRecords.map((r) => ({ ...r })),\n)\n\nconst searchQuery = ref('')\nconst selectedTypeFilter = ref<string>('ALL')\nconst isAddOpen = ref(props.initialAddOpen)\nconst isZoneFileOpen = ref(false)\nconst editingId = ref<string | null>(null)\n\n// Form fields\nconst formType = ref<DnsRecordType>('A')\nconst formName = ref('')\nconst formContent = ref('')\nconst formTtl = ref('Auto')\nconst formProxied = ref(true)\nconst formPriority = ref(10)\nconst formComment = ref('')\n\nconst typeBadgeStyles: Record<DnsRecordType, { bg: string; text: string; border: string }> = {\n  A: {\n    bg: 'bg-info/10',\n    text: 'text-info',\n    border: 'border-info/30',\n  },\n  AAAA: {\n    bg: 'bg-chart-1/10',\n    text: 'text-chart-1',\n    border: 'border-chart-1/30',\n  },\n  CNAME: {\n    bg: 'bg-chart-2/10',\n    text: 'text-chart-2',\n    border: 'border-chart-2/30',\n  },\n  MX: {\n    bg: 'bg-warning/10',\n    text: 'text-warning',\n    border: 'border-warning/30',\n  },\n  TXT: {\n    bg: 'bg-success/10',\n    text: 'text-success',\n    border: 'border-success/30',\n  },\n  CAA: {\n    bg: 'bg-info/10',\n    text: 'text-info',\n    border: 'border-info/30',\n  },\n}\n\nconst placeholderMap: Record<DnsRecordType, string> = {\n  A: 'e.g. 76.76.21.21',\n  AAAA: 'e.g. 2606:4700:3038::6815:1515',\n  CNAME: 'e.g. cname.vercel-dns.com',\n  MX: 'e.g. mail.google.com',\n  TXT: 'e.g. v=spf1 include:_spf.google.com ~all',\n  CAA: 'e.g. 0 issue \"letsencrypt.org\"',\n}\n\n// When type changes, auto-set proxy capability\nwatch(formType, (newType) => {\n  if (['MX', 'TXT', 'CAA'].includes(newType)) {\n    formProxied.value = false\n  } else if (!editingId.value) {\n    formProxied.value = true\n  }\n})\n\nconst filteredRecords = computed(() => {\n  const query = searchQuery.value.trim().toLowerCase()\n  return records.value.filter((record) => {\n    const matchesType = selectedTypeFilter.value === 'ALL' || record.type === selectedTypeFilter.value\n    const matchesQuery =\n      !query ||\n      record.name.toLowerCase().includes(query) ||\n      record.content.toLowerCase().includes(query) ||\n      record.type.toLowerCase().includes(query) ||\n      (record.comment && record.comment.toLowerCase().includes(query))\n    return matchesType && matchesQuery\n  })\n})\n\nconst copiedKey = ref<string | null>(null)\nlet copyTimeout: number | undefined\n\nasync function copyText(key: string, text: string) {\n  try {\n    await navigator.clipboard.writeText(text)\n    copiedKey.value = key\n    window.clearTimeout(copyTimeout)\n    copyTimeout = window.setTimeout(() => {\n      copiedKey.value = null\n    }, 1600)\n  } catch {\n    // Clipboard unavailable\n  }\n}\n\nfunction toggleProxy(id: string) {\n  const rec = records.value.find((r) => r.id === id)\n  if (rec && ['A', 'AAAA', 'CNAME'].includes(rec.type)) {\n    rec.proxied = !rec.proxied\n  }\n}\n\nfunction deleteRecord(id: string) {\n  records.value = records.value.filter((r) => r.id !== id)\n  if (editingId.value === id) {\n    cancelForm()\n  }\n}\n\nfunction startAdd() {\n  editingId.value = null\n  formType.value = 'A'\n  formName.value = ''\n  formContent.value = ''\n  formTtl.value = 'Auto'\n  formProxied.value = true\n  formPriority.value = 10\n  formComment.value = ''\n  isAddOpen.value = true\n}\n\nfunction startEdit(record: DnsRecord) {\n  editingId.value = record.id\n  formType.value = record.type\n  formName.value = record.name\n  formContent.value = record.content\n  formTtl.value = record.ttl\n  formProxied.value = record.proxied\n  formPriority.value = record.priority ?? 10\n  formComment.value = record.comment ?? ''\n  isAddOpen.value = true\n}\n\nfunction cancelForm() {\n  isAddOpen.value = false\n  editingId.value = null\n}\n\nfunction saveRecord() {\n  const name = formName.value.trim() || '@'\n  const content = formContent.value.trim()\n  if (!content) return\n\n  if (editingId.value) {\n    const idx = records.value.findIndex((r) => r.id === editingId.value)\n    if (idx !== -1) {\n      records.value[idx] = {\n        ...records.value[idx],\n        type: formType.value,\n        name,\n        content,\n        ttl: formTtl.value,\n        proxied: ['A', 'AAAA', 'CNAME'].includes(formType.value) ? formProxied.value : false,\n        priority: formType.value === 'MX' ? Number(formPriority.value) : undefined,\n        comment: formComment.value.trim() || undefined,\n      }\n    }\n  } else {\n    const newRecord: DnsRecord = {\n      id: `rec-${Date.now()}`,\n      type: formType.value,\n      name,\n      content,\n      ttl: formTtl.value,\n      proxied: ['A', 'AAAA', 'CNAME'].includes(formType.value) ? formProxied.value : false,\n      priority: formType.value === 'MX' ? Number(formPriority.value) : undefined,\n      comment: formComment.value.trim() || undefined,\n    }\n    records.value.unshift(newRecord)\n  }\n\n  cancelForm()\n}\n\nconst zoneFileContent = computed(() => {\n  const date = new Date().toISOString().split('T')[0].replace(/-/g, '')\n  const lines: string[] = [\n    `; BIND zone file for ${props.domain}`,\n    `; Generated on ${new Date().toUTCString()}`,\n    `$ORIGIN ${props.domain}.`,\n    `$TTL 3600`,\n    ``,\n    `; SOA Record`,\n    `@       IN  SOA  ${props.nameservers[0]}. hostmaster.${props.domain}. (`,\n    `                 ${date}01 ; Serial`,\n    `                 7200       ; Refresh (2 hours)`,\n    `                 3600       ; Retry (1 hour)`,\n    `                 1209600    ; Expire (2 weeks)`,\n    `                 3600       ; Minimum TTL (1 hour)`,\n    `)`,\n    ``,\n    `; Nameservers`,\n  ]\n\n  props.nameservers.forEach((ns) => {\n    lines.push(`@       IN  NS   ${ns}.`)\n  })\n\n  lines.push('', '; Resource Records')\n\n  for (const r of records.value) {\n    const recName = (r.name === '@' ? '@' : r.name).padEnd(12, ' ')\n    const ttlValue = r.ttl === 'Auto' ? '3600' : r.ttl.replace('s', '')\n    const ttlStr = ttlValue.padEnd(6, ' ')\n    const typeStr = r.type.padEnd(7, ' ')\n\n    if (r.type === 'MX') {\n      const prio = String(r.priority ?? 10).padEnd(4, ' ')\n      lines.push(`${recName} ${ttlStr} IN  ${typeStr} ${prio} ${r.content}.`)\n    } else if (r.type === 'TXT') {\n      lines.push(`${recName} ${ttlStr} IN  ${typeStr} \"${r.content}\"`)\n    } else if (r.type === 'CNAME') {\n      lines.push(`${recName} ${ttlStr} IN  ${typeStr} ${r.content}.`)\n    } else {\n      lines.push(`${recName} ${ttlStr} IN  ${typeStr} ${r.content}`)\n    }\n  }\n\n  return lines.join('\\n')\n})\n\nfunction downloadZoneFile() {\n  const blob = new Blob([zoneFileContent.value], { type: 'text/plain;charset=utf-8' })\n  const url = URL.createObjectURL(blob)\n  const a = document.createElement('a')\n  a.href = url\n  a.download = `${props.domain}.zone`\n  document.body.appendChild(a)\n  a.click()\n  document.body.removeChild(a)\n  URL.revokeObjectURL(url)\n}\n\nfunction resolveHostname(recordName: string): string {\n  if (recordName === '@') return props.domain\n  return `${recordName}.${props.domain}`\n}\n</script>\n\n<template>\n  <div data-slot=\"dns-record-manager\" :class=\"cn('w-full space-y-6', props.class)\">\n    <!-- Domain Header & Status Card -->\n    <Card class=\"border-border bg-card shadow-xs\">\n      <CardHeader class=\"pb-4\">\n        <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div class=\"space-y-1.5\">\n            <div class=\"flex flex-wrap items-center gap-2.5\">\n              <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                <Globe class=\"size-4.5\" />\n              </div>\n              <h2 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">\n                {{ props.domain }}\n              </h2>\n              <Badge\n                v-if=\"props.dnssecEnabled\"\n                variant=\"outline\"\n                class=\"border-success/30 bg-success/10 text-success gap-1.5 py-0.5\"\n              >\n                <span class=\"bg-success size-1.5 rounded-full\" />\n                <ShieldCheck class=\"size-3.5\" />\n                Active · DNSSEC Enabled\n              </Badge>\n              <Badge v-else variant=\"outline\" class=\"border-warning/30 bg-warning/10 text-warning gap-1.5 py-0.5\">\n                <span class=\"bg-warning size-1.5 rounded-full\" />\n                <ShieldAlert class=\"size-3.5\" />\n                Active · DNSSEC Inactive\n              </Badge>\n            </div>\n            <p class=\"text-muted-foreground text-xs sm:text-sm\">\n              Authoritative DNS routing and global edge proxy management for {{ props.domain }}.\n            </p>\n          </div>\n\n          <!-- Header CTA Buttons -->\n          <div class=\"flex items-center gap-2\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              class=\"h-8 gap-1.5 text-xs font-medium\"\n              @click=\"isZoneFileOpen = !isZoneFileOpen\"\n            >\n              <FileCode class=\"size-3.5\" />\n              {{ isZoneFileOpen ? 'Hide Zone File' : 'Export Zone File' }}\n            </Button>\n            <Button size=\"sm\" class=\"h-8 gap-1.5 text-xs font-medium\" @click=\"startAdd\">\n              <Plus class=\"size-3.5\" />\n              Add Record\n            </Button>\n          </div>\n        </div>\n\n        <Separator class=\"my-4\" />\n\n        <!-- Nameserver Inspector Bar -->\n        <div class=\"flex flex-wrap items-center justify-between gap-3 text-xs\">\n          <div class=\"flex flex-wrap items-center gap-2\">\n            <span class=\"text-muted-foreground font-medium\">Nameservers:</span>\n            <div class=\"flex flex-wrap items-center gap-1.5\">\n              <button\n                v-for=\"ns in props.nameservers\"\n                :key=\"ns\"\n                type=\"button\"\n                :aria-label=\"`Copy nameserver ${ns}`\"\n                class=\"border-border bg-muted/60 hover:bg-muted focus-visible:ring-ring/50 group text-foreground flex min-h-6 items-center gap-1.5 rounded-md border px-2.5 py-1 font-mono text-xs transition-colors focus-visible:ring-[2px] focus-visible:outline-none\"\n                @click=\"copyText(ns, ns)\"\n              >\n                <span>{{ ns }}</span>\n                <Check v-if=\"copiedKey === ns\" class=\"text-success size-3\" />\n                <Copy v-else class=\"text-muted-foreground group-hover:text-foreground size-3\" />\n              </button>\n            </div>\n          </div>\n          <div class=\"text-muted-foreground flex items-center gap-2\">\n            <span class=\"bg-success size-2 animate-pulse rounded-full\" />\n            <span>Propagation: 100% Synced</span>\n          </div>\n        </div>\n      </CardHeader>\n    </Card>\n\n    <!-- Zone File Export Card (Collapsible) -->\n    <Card v-if=\"isZoneFileOpen\" class=\"border-border bg-card shadow-xs\">\n      <CardHeader class=\"pb-3\">\n        <div class=\"flex items-center justify-between\">\n          <div>\n            <CardTitle class=\"text-sm font-semibold sm:text-base\">BIND RFC 1035 Zone File</CardTitle>\n            <CardDescription class=\"text-xs\">\n              Complete DNS zone mapping for {{ props.domain }} ready for import into Route53, Bind9, or Cloudflare.\n            </CardDescription>\n          </div>\n          <Button\n            variant=\"ghost\"\n            size=\"icon-sm\"\n            class=\"size-7\"\n            aria-label=\"Close zone file viewer\"\n            @click=\"isZoneFileOpen = false\"\n          >\n            <X class=\"size-4\" />\n          </Button>\n        </div>\n      </CardHeader>\n      <CardContent class=\"space-y-3 pt-0\">\n        <div class=\"relative\">\n          <pre\n            class=\"border-border bg-muted/70 text-foreground max-h-56 overflow-x-auto rounded-lg border p-3 font-mono text-xs leading-relaxed select-all\"\n            >{{ zoneFileContent }}</pre\n          >\n        </div>\n        <div class=\"flex items-center justify-end gap-2\">\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            class=\"h-8 gap-1.5 text-xs\"\n            @click=\"copyText('zone-file', zoneFileContent)\"\n          >\n            <Check v-if=\"copiedKey === 'zone-file'\" class=\"text-success size-3.5\" />\n            <Copy v-else class=\"size-3.5\" />\n            {{ copiedKey === 'zone-file' ? 'Copied Zone File' : 'Copy Zone Content' }}\n          </Button>\n          <Button aria-label=\"Download attachment\" size=\"sm\" class=\"h-8 gap-1.5 text-xs\" @click=\"downloadZoneFile\">\n            <Download class=\"size-3.5\" />\n            Download .zone File\n          </Button>\n        </div>\n      </CardContent>\n    </Card>\n\n    <!-- Add / Edit DNS Record Card -->\n    <Card v-if=\"isAddOpen\" class=\"border-primary/30 bg-muted/20 shadow-xs\">\n      <CardHeader class=\"pb-4\">\n        <div class=\"flex items-center justify-between\">\n          <div>\n            <CardTitle class=\"text-base font-semibold\">\n              {{ editingId ? 'Edit DNS Record' : 'Add New DNS Record' }}\n            </CardTitle>\n            <CardDescription class=\"text-xs\">\n              {{\n                editingId\n                  ? 'Update configuration, target destination, or edge proxy status.'\n                  : `Configure host routing, MX exchangers, or verification TXT tags for ${props.domain}.`\n              }}\n            </CardDescription>\n          </div>\n          <Button variant=\"ghost\" size=\"icon-sm\" class=\"size-7\" aria-label=\"Cancel record form\" @click=\"cancelForm\">\n            <X class=\"size-4\" />\n          </Button>\n        </div>\n      </CardHeader>\n      <CardContent class=\"space-y-4 pt-0\">\n        <div class=\"grid grid-cols-1 gap-3 sm:grid-cols-12\">\n          <!-- Type Select -->\n          <div class=\"sm:col-span-3\">\n            <label for=\"dns-type\" class=\"text-foreground mb-1.5 block text-xs font-medium\"> Type </label>\n            <Select v-model=\"formType\">\n              <SelectTrigger id=\"dns-type\" class=\"w-full [&_svg]:shrink-0 [&>span]:truncate\">\n                <SelectValue placeholder=\"Record type\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"A\">A (IPv4 Address)</SelectItem>\n                <SelectItem value=\"AAAA\">AAAA (IPv6 Address)</SelectItem>\n                <SelectItem value=\"CNAME\">CNAME (Canonical Alias)</SelectItem>\n                <SelectItem value=\"MX\">MX (Mail Exchanger)</SelectItem>\n                <SelectItem value=\"TXT\">TXT (Text Record)</SelectItem>\n                <SelectItem value=\"CAA\">CAA (Cert Authority)</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n\n          <!-- Name Input -->\n          <div class=\"sm:col-span-5\">\n            <label for=\"dns-name\" class=\"text-foreground mb-1.5 block text-xs font-medium\">\n              Name / Subdomain\n              <span class=\"text-muted-foreground font-normal\">(@ for root)</span>\n            </label>\n            <Input id=\"dns-name\" v-model=\"formName\" placeholder=\"@ or subdomain\" class=\"font-mono text-xs\" />\n          </div>\n\n          <!-- TTL Select -->\n          <div class=\"sm:col-span-4\">\n            <label for=\"dns-ttl\" class=\"text-foreground mb-1.5 block text-xs font-medium\"> TTL </label>\n            <Select v-model=\"formTtl\">\n              <SelectTrigger id=\"dns-ttl\" class=\"w-full [&_svg]:shrink-0 [&>span]:truncate\">\n                <SelectValue placeholder=\"TTL\" />\n              </SelectTrigger>\n              <SelectContent>\n                <SelectItem value=\"Auto\">Auto (Automatic)</SelectItem>\n                <SelectItem value=\"60s\">1 min (60s)</SelectItem>\n                <SelectItem value=\"300s\">5 mins (300s)</SelectItem>\n                <SelectItem value=\"1800s\">30 mins (1800s)</SelectItem>\n                <SelectItem value=\"3600s\">1 hour (3600s)</SelectItem>\n                <SelectItem value=\"86400s\">1 day (86400s)</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n        </div>\n\n        <div class=\"grid grid-cols-1 gap-3\" :class=\"formType === 'MX' ? 'sm:grid-cols-12' : ''\">\n          <!-- Content / Target Input -->\n          <div :class=\"formType === 'MX' ? 'sm:col-span-9' : 'w-full'\">\n            <label for=\"dns-content\" class=\"text-foreground mb-1.5 block text-xs font-medium\">\n              {{\n                formType === 'A'\n                  ? 'IPv4 Address'\n                  : formType === 'AAAA'\n                    ? 'IPv6 Address'\n                    : formType === 'CNAME'\n                      ? 'Target Domain'\n                      : formType === 'MX'\n                        ? 'Mail Server Hostname'\n                        : formType === 'TXT'\n                          ? 'TXT Content / Value'\n                          : 'Target / Tag Value'\n              }}\n            </label>\n            <Input\n              id=\"dns-content\"\n              v-model=\"formContent\"\n              :placeholder=\"placeholderMap[formType]\"\n              class=\"font-mono text-xs\"\n            />\n          </div>\n\n          <!-- Priority Input for MX -->\n          <div v-if=\"formType === 'MX'\" class=\"sm:col-span-3\">\n            <label for=\"dns-priority\" class=\"text-foreground mb-1.5 block text-xs font-medium\"> Priority </label>\n            <Input\n              id=\"dns-priority\"\n              v-model=\"formPriority\"\n              type=\"number\"\n              min=\"0\"\n              max=\"65535\"\n              placeholder=\"10\"\n              class=\"font-mono text-xs\"\n            />\n          </div>\n        </div>\n\n        <!-- Optional Comment -->\n        <div>\n          <label for=\"dns-comment\" class=\"text-foreground mb-1.5 block text-xs font-medium\">\n            Comment <span class=\"text-muted-foreground font-normal\">(optional note)</span>\n          </label>\n          <Input\n            id=\"dns-comment\"\n            v-model=\"formComment\"\n            placeholder=\"e.g. Production ingress or verification tag\"\n            class=\"text-xs\"\n          />\n        </div>\n\n        <!-- Proxy Switch (for A, AAAA, CNAME) -->\n        <div\n          v-if=\"['A', 'AAAA', 'CNAME'].includes(formType)\"\n          class=\"border-border bg-background/80 flex items-center justify-between rounded-lg border p-3\"\n        >\n          <div class=\"space-y-0.5\">\n            <div class=\"flex items-center gap-2\">\n              <Cloud class=\"text-warning size-4\" />\n              <span class=\"text-foreground text-xs font-medium\">Proxy Status</span>\n            </div>\n            <p class=\"text-muted-foreground text-xs\">\n              {{\n                formProxied\n                  ? 'Proxied: Accelerate traffic and protect origin IP behind Cloudflare edge.'\n                  : 'DNS only: Direct traffic route without edge proxy caching.'\n              }}\n            </p>\n          </div>\n          <Switch id=\"proxy-toggle\" v-model=\"formProxied\" />\n        </div>\n\n        <!-- Form Actions -->\n        <div class=\"flex items-center justify-end gap-2 pt-2\">\n          <Button variant=\"outline\" size=\"sm\" class=\"h-8 text-xs\" @click=\"cancelForm\"> Cancel </Button>\n          <Button size=\"sm\" class=\"h-8 text-xs\" :disabled=\"!formContent.trim()\" @click=\"saveRecord\">\n            {{ editingId ? 'Update Record' : 'Save Record' }}\n          </Button>\n        </div>\n      </CardContent>\n    </Card>\n\n    <!-- DNS Records Management Section -->\n    <div class=\"space-y-3\">\n      <!-- Toolbar Filter & Search -->\n      <div class=\"flex flex-col gap-2.5 sm:flex-row sm:items-center sm:justify-between\">\n        <div class=\"relative w-full max-w-sm\">\n          <Search\n            class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2\"\n          />\n          <Input\n            v-model=\"searchQuery\"\n            placeholder=\"Search records by name, target, or content...\"\n            class=\"h-8.5 pl-8 text-xs\"\n          />\n        </div>\n\n        <!-- Type Filter Buttons / Select -->\n        <div class=\"flex items-center gap-1.5 overflow-x-auto pb-1 sm:pb-0\">\n          <button\n            type=\"button\"\n            :class=\"\n              cn(\n                'min-h-6 rounded-md px-2.5 py-1 text-xs font-medium transition-colors',\n                selectedTypeFilter === 'ALL'\n                  ? 'bg-primary text-primary-foreground shadow-xs'\n                  : 'bg-muted/70 text-muted-foreground hover:bg-muted hover:text-foreground',\n              )\n            \"\n            @click=\"selectedTypeFilter = 'ALL'\"\n          >\n            All ({{ records.length }})\n          </button>\n          <button\n            v-for=\"t in ['A', 'CNAME', 'MX', 'TXT', 'AAAA', 'CAA'] as DnsRecordType[]\"\n            :key=\"t\"\n            type=\"button\"\n            :class=\"\n              cn(\n                'min-h-6 min-w-6 rounded-md px-2 py-1 font-mono text-xs font-medium transition-colors',\n                selectedTypeFilter === t\n                  ? 'bg-primary text-primary-foreground shadow-xs'\n                  : 'bg-muted/70 text-muted-foreground hover:bg-muted hover:text-foreground',\n              )\n            \"\n            @click=\"selectedTypeFilter = t\"\n          >\n            {{ t }}\n          </button>\n        </div>\n      </div>\n\n      <!-- DNS Records Table -->\n      <div class=\"border-border bg-card overflow-hidden rounded-lg border shadow-xs\">\n        <div class=\"overflow-x-auto\">\n          <Table>\n            <TableHeader>\n              <TableRow class=\"bg-muted/40\">\n                <TableHead class=\"w-24\">Type</TableHead>\n                <TableHead class=\"min-w-[140px]\">Name</TableHead>\n                <TableHead class=\"min-w-[240px]\">Target Content</TableHead>\n                <TableHead class=\"w-28 text-center\">Proxy Status</TableHead>\n                <TableHead class=\"w-20\">TTL</TableHead>\n                <TableHead class=\"w-12 text-right\"><span class=\"sr-only\">Actions</span></TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              <TableRow\n                v-for=\"record in filteredRecords\"\n                :key=\"record.id\"\n                class=\"hover:bg-muted/30 transition-colors\"\n                :class=\"editingId === record.id ? 'bg-primary/5' : ''\"\n              >\n                <!-- Record Type Badge -->\n                <TableCell>\n                  <span\n                    :class=\"\n                      cn(\n                        'inline-flex items-center justify-center rounded-md border px-2 py-0.5 font-mono text-xs font-bold',\n                        typeBadgeStyles[record.type].bg,\n                        typeBadgeStyles[record.type].text,\n                        typeBadgeStyles[record.type].border,\n                      )\n                    \"\n                  >\n                    {{ record.type }}\n                  </span>\n                </TableCell>\n\n                <!-- Name & Hostname helper -->\n                <TableCell>\n                  <div class=\"flex flex-col\">\n                    <span class=\"text-foreground font-mono text-xs font-semibold\">\n                      {{ record.name }}\n                    </span>\n                    <span class=\"text-muted-foreground truncate font-mono text-xs\">\n                      {{ resolveHostname(record.name) }}\n                    </span>\n                  </div>\n                </TableCell>\n\n                <!-- Target Content with Copy button -->\n                <TableCell>\n                  <div class=\"flex items-center gap-2\">\n                    <div class=\"min-w-0 flex-1\">\n                      <div class=\"flex items-center gap-1.5\">\n                        <span\n                          v-if=\"record.type === 'MX'\"\n                          class=\"border-border bg-muted text-muted-foreground rounded px-1 font-mono text-xs\"\n                        >\n                          Pri {{ record.priority }}\n                        </span>\n                        <code class=\"text-foreground/90 font-mono text-xs break-all select-all\">\n                          {{ record.content }}\n                        </code>\n                      </div>\n                      <p v-if=\"record.comment\" class=\"text-muted-foreground mt-0.5 truncate text-xs\">\n                        {{ record.comment }}\n                      </p>\n                    </div>\n\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon-sm\"\n                      class=\"text-muted-foreground hover:text-foreground size-7 shrink-0\"\n                      :aria-label=\"`Copy content for ${record.name}`\"\n                      @click=\"copyText(record.id, record.content)\"\n                    >\n                      <Check v-if=\"copiedKey === record.id\" class=\"text-success size-3.5\" />\n                      <Copy v-else class=\"size-3.5\" />\n                    </Button>\n                  </div>\n                </TableCell>\n\n                <!-- Proxy Status Switch / Cloud badge -->\n                <TableCell class=\"text-center\">\n                  <div v-if=\"['A', 'AAAA', 'CNAME'].includes(record.type)\" class=\"flex justify-center\">\n                    <button\n                      type=\"button\"\n                      :aria-label=\"`Toggle proxy status for ${record.name}, currently ${record.proxied ? 'proxied' : 'dns only'}`\"\n                      :class=\"\n                        cn(\n                          'group inline-flex min-h-6 items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus-visible:ring-[2px] focus-visible:outline-none',\n                          record.proxied\n                            ? 'border-warning/30 bg-warning/10 text-warning hover:bg-warning/20 text-warning'\n                            : 'border-border bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground',\n                        )\n                      \"\n                      @click=\"toggleProxy(record.id)\"\n                    >\n                      <Cloud v-if=\"record.proxied\" class=\"fill-warning text-warning size-3.5\" />\n                      <CloudOff v-else class=\"text-muted-foreground group-hover:text-foreground size-3.5\" />\n                      <span>{{ record.proxied ? 'Proxied' : 'DNS only' }}</span>\n                    </button>\n                  </div>\n                  <div v-else class=\"flex justify-center\">\n                    <span class=\"text-muted-foreground/60 inline-flex items-center gap-1 font-mono text-xs\">\n                      <CloudOff class=\"size-3\" />\n                      DNS only\n                    </span>\n                  </div>\n                </TableCell>\n\n                <!-- TTL Badge -->\n                <TableCell>\n                  <Badge variant=\"outline\" class=\"border-border bg-muted/40 font-mono text-xs font-normal\">\n                    {{ record.ttl }}\n                  </Badge>\n                </TableCell>\n\n                <!-- Row Actions Dropdown -->\n                <TableCell class=\"text-right\">\n                  <DropdownMenu>\n                    <DropdownMenuTrigger as-child>\n                      <Button variant=\"ghost\" size=\"icon-sm\" class=\"text-muted-foreground size-7 p-0\">\n                        <MoreHorizontal class=\"size-4\" />\n                        <span class=\"sr-only\">Open record actions</span>\n                      </Button>\n                    </DropdownMenuTrigger>\n                    <DropdownMenuContent align=\"end\" class=\"w-44\">\n                      <DropdownMenuItem @click=\"startEdit(record)\">\n                        <Edit2 class=\"mr-2 size-3.5\" />\n                        Edit Record\n                      </DropdownMenuItem>\n                      <DropdownMenuItem @click=\"copyText(record.id, record.content)\">\n                        <Copy class=\"mr-2 size-3.5\" />\n                        Copy Target\n                      </DropdownMenuItem>\n                      <DropdownMenuItem\n                        @click=\"copyText(`${record.id}-bind`, `${record.name} IN ${record.type} ${record.content}`)\"\n                      >\n                        <FileCode class=\"mr-2 size-3.5\" />\n                        Copy BIND Row\n                      </DropdownMenuItem>\n                      <DropdownMenuSeparator />\n                      <DropdownMenuItem\n                        class=\"text-destructive focus:text-destructive\"\n                        @click=\"deleteRecord(record.id)\"\n                      >\n                        <Trash2 class=\"mr-2 size-3.5\" />\n                        Delete Record\n                      </DropdownMenuItem>\n                    </DropdownMenuContent>\n                  </DropdownMenu>\n                </TableCell>\n              </TableRow>\n\n              <!-- Empty State -->\n              <TableRow v-if=\"filteredRecords.length === 0\">\n                <TableCell colspan=\"6\" class=\"h-32 text-center\">\n                  <div class=\"flex flex-col items-center justify-center gap-1.5 text-center\">\n                    <Globe class=\"text-muted-foreground/50 size-8\" />\n                    <p class=\"text-foreground text-sm font-medium\">No DNS records found</p>\n                    <p class=\"text-muted-foreground max-w-sm text-xs\">\n                      {{\n                        searchQuery\n                          ? 'No records match your search filter query.'\n                          : 'No records configured for this domain yet. Add your first record to begin routing.'\n                      }}\n                    </p>\n                    <Button size=\"sm\" class=\"mt-2 h-7 gap-1 text-xs\" @click=\"startAdd\">\n                      <Plus class=\"size-3\" />\n                      Add DNS Record\n                    </Button>\n                  </div>\n                </TableCell>\n              </TableRow>\n            </TableBody>\n          </Table>\n        </div>\n      </div>\n\n      <!-- Table Footer info -->\n      <div class=\"text-muted-foreground flex flex-wrap items-center justify-between gap-2 px-1 text-xs\">\n        <p>\n          Showing <span class=\"text-foreground font-medium\">{{ filteredRecords.length }}</span> of\n          <span class=\"text-foreground font-medium\">{{ records.length }}</span> configured records\n        </p>\n        <div class=\"flex items-center gap-4\">\n          <span class=\"flex items-center gap-1.5\">\n            <span class=\"bg-warning size-2 rounded-full\" />\n            {{ records.filter((r) => r.proxied).length }} Proxied\n          </span>\n          <span class=\"flex items-center gap-1.5\">\n            <span class=\"bg-muted-foreground size-2 rounded-full\" />\n            {{ records.filter((r) => !r.proxied).length }} DNS Only\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/DnsRecordManager.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/dropdown-menu.json",
    "https://uipkge.dev/r/vue/input.json",
    "https://uipkge.dev/r/vue/select.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/switch.json",
    "https://uipkge.dev/r/vue/table.json"
  ],
  "description": "Cloudflare/Vercel style DNS records management table and nameserver inspector with type badges, proxy toggle, copyable values, zone file exporter, and add/edit record panel.",
  "categories": [
    "devops",
    "dashboard",
    "data"
  ]
}