|
|
@@ -133,26 +133,27 @@ onMounted(loadData)
|
|
|
|
|
|
<template>
|
|
|
<div class="space-y-6">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-start justify-between gap-4">
|
|
|
<div>
|
|
|
<h1 class="text-2xl font-bold tracking-tight">DNS Records</h1>
|
|
|
<p class="text-muted-foreground">Manage DNS records across your zones</p>
|
|
|
</div>
|
|
|
- <div class="flex gap-2">
|
|
|
+ <div class="flex shrink-0 gap-2">
|
|
|
<Button variant="outline" @click="manualSync" :disabled="syncing">
|
|
|
<RefreshCw class="mr-2 h-4 w-4" :class="{ 'animate-spin': syncing }" />
|
|
|
- Sync
|
|
|
+ <span class="hidden sm:inline">Sync</span>
|
|
|
</Button>
|
|
|
<Button class="bg-orange-500 hover:bg-orange-500/90 text-white" @click="openAdd">
|
|
|
<Plus class="mr-2 h-4 w-4" />
|
|
|
- Add Record
|
|
|
+ <span class="hidden sm:inline">Add Record</span>
|
|
|
+ <span class="sm:hidden">Add</span>
|
|
|
</Button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center gap-4">
|
|
|
<Select v-model="selectedZone">
|
|
|
- <SelectTrigger class="w-[200px]">
|
|
|
+ <SelectTrigger class="w-full sm:w-[200px]">
|
|
|
<SelectValue placeholder="Filter by zone" />
|
|
|
</SelectTrigger>
|
|
|
<SelectContent>
|
|
|
@@ -165,7 +166,8 @@ onMounted(loadData)
|
|
|
</div>
|
|
|
|
|
|
<template v-if="loading">
|
|
|
- <div class="rounded-md border">
|
|
|
+ <!-- Desktop skeleton -->
|
|
|
+ <div class="hidden sm:block rounded-md border">
|
|
|
<Table>
|
|
|
<TableHeader>
|
|
|
<TableRow>
|
|
|
@@ -186,6 +188,14 @@ onMounted(loadData)
|
|
|
</TableBody>
|
|
|
</Table>
|
|
|
</div>
|
|
|
+ <!-- Mobile skeleton -->
|
|
|
+ <div class="sm:hidden space-y-3">
|
|
|
+ <div v-for="i in 3" :key="i" class="rounded-lg border p-4 space-y-2">
|
|
|
+ <Skeleton class="h-5 w-40" />
|
|
|
+ <Skeleton class="h-4 w-full" />
|
|
|
+ <Skeleton class="h-4 w-24" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="filteredRecords.length === 0">
|
|
|
@@ -203,6 +213,7 @@ onMounted(loadData)
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
+ <!-- Dynamic Records -->
|
|
|
<Collapsible :default-open="true" v-slot="{ open }">
|
|
|
<div class="flex items-center justify-between">
|
|
|
<div class="space-y-1">
|
|
|
@@ -216,7 +227,8 @@ onMounted(loadData)
|
|
|
</CollapsibleTrigger>
|
|
|
</div>
|
|
|
<CollapsibleContent>
|
|
|
- <div class="rounded-md border mt-3">
|
|
|
+ <!-- Desktop table -->
|
|
|
+ <div class="hidden sm:block rounded-md border mt-3">
|
|
|
<Table>
|
|
|
<TableHeader>
|
|
|
<TableRow>
|
|
|
@@ -284,9 +296,64 @@ onMounted(loadData)
|
|
|
</TableBody>
|
|
|
</Table>
|
|
|
</div>
|
|
|
+ <!-- Mobile cards -->
|
|
|
+ <div class="sm:hidden mt-3 space-y-3">
|
|
|
+ <div v-if="dynamicRecords.length === 0" class="rounded-lg border p-6 text-center text-sm text-muted-foreground">
|
|
|
+ No dynamic records. Use the actions menu to set a record as dynamic.
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="record in dynamicRecords"
|
|
|
+ :key="record.id"
|
|
|
+ class="rounded-lg border p-4"
|
|
|
+ >
|
|
|
+ <div class="flex items-start justify-between gap-2">
|
|
|
+ <div class="min-w-0 space-y-1">
|
|
|
+ <div class="flex items-center gap-2 flex-wrap">
|
|
|
+ <p class="font-semibold truncate">{{ record.name }}</p>
|
|
|
+ <Badge variant="outline">{{ record.type }}</Badge>
|
|
|
+ <Badge
|
|
|
+ :class="record.proxied === 1
|
|
|
+ ? 'bg-green-500/15 text-green-700 dark:text-green-400 border-green-500/30'
|
|
|
+ : 'bg-orange-500/15 text-orange-700 dark:text-orange-400 border-orange-500/30'"
|
|
|
+ variant="outline"
|
|
|
+ >
|
|
|
+ {{ record.proxied === 1 ? 'Proxied' : 'Not proxied' }}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+ <p class="text-xs font-mono text-muted-foreground break-all">{{ record.content }}</p>
|
|
|
+ <p class="text-xs text-muted-foreground">Zone: {{ record.zone_name }}</p>
|
|
|
+ <p v-if="record.last_updated_at" class="text-xs text-muted-foreground">
|
|
|
+ Updated: {{ formatDate(record.last_updated_at) }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <DropdownMenu>
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
+ <Button variant="ghost" size="icon" class="h-8 w-8 shrink-0">
|
|
|
+ <MoreHorizontal class="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ </DropdownMenuTrigger>
|
|
|
+ <DropdownMenuContent align="end">
|
|
|
+ <DropdownMenuItem @click="toggleStatic(record)">
|
|
|
+ <ArrowLeftRight class="mr-2 h-4 w-4" />
|
|
|
+ Set static
|
|
|
+ </DropdownMenuItem>
|
|
|
+ <DropdownMenuItem @click="openEdit(record)">
|
|
|
+ <Pencil class="mr-2 h-4 w-4" />
|
|
|
+ Edit
|
|
|
+ </DropdownMenuItem>
|
|
|
+ <DropdownMenuItem class="text-destructive" @click="openDelete(record)">
|
|
|
+ <Trash2 class="mr-2 h-4 w-4" />
|
|
|
+ Delete
|
|
|
+ </DropdownMenuItem>
|
|
|
+ </DropdownMenuContent>
|
|
|
+ </DropdownMenu>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</CollapsibleContent>
|
|
|
</Collapsible>
|
|
|
|
|
|
+ <!-- Static Records -->
|
|
|
<Collapsible :default-open="true" v-slot="{ open }">
|
|
|
<div class="flex items-center justify-between">
|
|
|
<div class="space-y-1">
|
|
|
@@ -300,7 +367,8 @@ onMounted(loadData)
|
|
|
</CollapsibleTrigger>
|
|
|
</div>
|
|
|
<CollapsibleContent>
|
|
|
- <div class="rounded-md border mt-3">
|
|
|
+ <!-- Desktop table -->
|
|
|
+ <div class="hidden sm:block rounded-md border mt-3">
|
|
|
<Table>
|
|
|
<TableHeader>
|
|
|
<TableRow>
|
|
|
@@ -364,6 +432,57 @@ onMounted(loadData)
|
|
|
</TableBody>
|
|
|
</Table>
|
|
|
</div>
|
|
|
+ <!-- Mobile cards -->
|
|
|
+ <div class="sm:hidden mt-3 space-y-3">
|
|
|
+ <div v-if="staticRecords.length === 0" class="rounded-lg border p-6 text-center text-sm text-muted-foreground">
|
|
|
+ No static records.
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="record in staticRecords"
|
|
|
+ :key="record.id"
|
|
|
+ class="rounded-lg border p-4"
|
|
|
+ >
|
|
|
+ <div class="flex items-start justify-between gap-2">
|
|
|
+ <div class="min-w-0 space-y-1">
|
|
|
+ <div class="flex items-center gap-2 flex-wrap">
|
|
|
+ <p class="font-semibold truncate">{{ record.name }}</p>
|
|
|
+ <Badge variant="outline">{{ record.type }}</Badge>
|
|
|
+ <Badge
|
|
|
+ :class="record.proxied === 1
|
|
|
+ ? 'bg-green-500/15 text-green-700 dark:text-green-400 border-green-500/30'
|
|
|
+ : 'bg-orange-500/15 text-orange-700 dark:text-orange-400 border-orange-500/30'"
|
|
|
+ variant="outline"
|
|
|
+ >
|
|
|
+ {{ record.proxied === 1 ? 'Proxied' : 'Not proxied' }}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+ <p class="text-xs font-mono text-muted-foreground break-all">{{ record.content }}</p>
|
|
|
+ <p class="text-xs text-muted-foreground">Zone: {{ record.zone_name }}</p>
|
|
|
+ </div>
|
|
|
+ <DropdownMenu>
|
|
|
+ <DropdownMenuTrigger asChild>
|
|
|
+ <Button variant="ghost" size="icon" class="h-8 w-8 shrink-0">
|
|
|
+ <MoreHorizontal class="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ </DropdownMenuTrigger>
|
|
|
+ <DropdownMenuContent align="end">
|
|
|
+ <DropdownMenuItem @click="toggleStatic(record)">
|
|
|
+ <ArrowLeftRight class="mr-2 h-4 w-4" />
|
|
|
+ Set dynamic
|
|
|
+ </DropdownMenuItem>
|
|
|
+ <DropdownMenuItem @click="openEdit(record)">
|
|
|
+ <Pencil class="mr-2 h-4 w-4" />
|
|
|
+ Edit
|
|
|
+ </DropdownMenuItem>
|
|
|
+ <DropdownMenuItem class="text-destructive" @click="openDelete(record)">
|
|
|
+ <Trash2 class="mr-2 h-4 w-4" />
|
|
|
+ Delete
|
|
|
+ </DropdownMenuItem>
|
|
|
+ </DropdownMenuContent>
|
|
|
+ </DropdownMenu>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</CollapsibleContent>
|
|
|
</Collapsible>
|
|
|
</template>
|