jcloud/dashboard/src2/components/JsiteDomainDNSRecords.vue

390 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="space-y-6">
<!-- 标题和操作按钮 -->
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-medium text-gray-900">域名解析记录</h2>
<p class="mt-1 text-sm text-gray-500">管理域名的DNS解析记录</p>
</div>
<div class="flex gap-2">
<Button
@click="refreshRecords"
:loading="loading"
variant="outline"
size="sm"
>
<RefreshCwIcon class="h-4 w-4 mr-1" />
刷新
</Button>
<Button
@click="showAddRecordDialog"
variant="solid"
size="sm"
>
<PlusIcon class="h-4 w-4 mr-1" />
添加记录
</Button>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-12">
<div class="flex items-center space-x-2">
<Loader2Icon class="h-5 w-5 animate-spin text-gray-500" />
<span class="text-gray-500">正在加载DNS记录...</span>
</div>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="rounded-md border border-red-200 bg-red-50 p-4">
<div class="flex items-center">
<AlertCircleIcon class="h-5 w-5 text-red-400 mr-2" />
<div>
<h3 class="text-sm font-medium text-red-800">加载失败</h3>
<p class="text-sm text-red-700 mt-1">{{ error }}</p>
</div>
</div>
</div>
<!-- DNS记录列表 -->
<div v-else-if="dnsRecords.length > 0" class="space-y-4">
<div class="overflow-hidden rounded-md border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<input type="checkbox" class="rounded border-gray-300" />
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
主机名
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
类型
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
线路类型
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
对应值
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
TTL
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
优先级
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
状态
</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr v-for="record in dnsRecords" :key="record.id" class="hover:bg-gray-50">
<td class="px-4 py-4 whitespace-nowrap">
<input type="checkbox" class="rounded border-gray-300" />
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm font-mono text-gray-900">
{{ record.item || record.host || '@' }}
</td>
<td class="px-4 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
:class="{
'bg-green-100 text-green-800': record.type === 'A',
'bg-blue-100 text-blue-800': record.type === 'AAAA',
'bg-yellow-100 text-yellow-800': record.type === 'CNAME',
'bg-purple-100 text-purple-800': record.type === 'MX',
'bg-gray-100 text-gray-800': record.type === 'TXT',
'bg-orange-100 text-orange-800': record.type === 'NS',
'bg-gray-100 text-gray-800': !record.type
}">
{{ record.type || '未知' }}
</span>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
默认
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm font-mono text-gray-900 max-w-xs truncate" :title="record.value">
{{ record.value }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ record.ttl || '600' }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ record.type === 'MX' && record.level ? record.level : '-' }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
正常
</span>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm">
<div class="flex items-center space-x-1">
<Button
@click="editRecord(record)"
variant="ghost"
size="sm"
class="text-blue-600 hover:text-blue-700"
>
修改
</Button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 批量操作 -->
<div class="flex items-center justify-between bg-gray-50 px-4 py-3 rounded-md">
<div class="flex items-center space-x-4">
<input type="checkbox" class="rounded border-gray-300" />
<span class="text-sm text-gray-700">已选 0 条</span>
<div class="flex items-center space-x-2">
<Button variant="outline" size="sm">批量暂停</Button>
<Button variant="outline" size="sm">批量启用</Button>
<Button variant="outline" size="sm">批量修改</Button>
<Button variant="outline" size="sm">批量删除</Button>
</div>
</div>
<div class="flex items-center space-x-4">
<Button variant="link" size="sm">导出解析记录</Button>
<Button variant="link" size="sm">导入解析记录</Button>
</div>
</div>
<!-- 分页 -->
<div v-if="pagination.total > pagination.limit" class="flex items-center justify-between">
<div class="text-sm text-gray-700">
显示第 {{ (pagination.pageno - 1) * pagination.limit + 1 }} -
{{ Math.min(pagination.pageno * pagination.limit, pagination.total) }} 条,
共 {{ pagination.total }} 条记录
</div>
<div class="flex items-center space-x-2">
<Button
@click="changePage(pagination.pageno - 1)"
:disabled="pagination.pageno <= 1"
variant="outline"
size="sm"
>
上一页
</Button>
<span class="text-sm text-gray-700">
{{ pagination.pageno }} / {{ pagination.pagecount }}
</span>
<Button
@click="changePage(pagination.pageno + 1)"
:disabled="pagination.pageno >= pagination.pagecount"
variant="outline"
size="sm"
>
下一页
</Button>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-else class="text-center py-12">
<GlobeIcon class="mx-auto h-12 w-12 text-gray-400" />
<h3 class="mt-2 text-sm font-medium text-gray-900">暂无DNS记录</h3>
<p class="mt-1 text-sm text-gray-500">开始添加您的第一个DNS解析记录</p>
<div class="mt-6">
<Button @click="showAddRecordDialog" variant="solid">
<PlusIcon class="h-4 w-4 mr-1" />
添加记录
</Button>
</div>
</div>
</div>
</template>
<script>
import { getCachedDocumentResource, Badge, Button, createResource } from 'jingrow-ui';
import { h, defineAsyncComponent } from 'vue';
import { toast } from 'vue-sonner';
import { getToastErrorMessage } from '../utils/toast';
import { renderDialog, confirmDialog } from '../utils/components';
import RefreshCwIcon from '~icons/lucide/refresh-cw';
import PlusIcon from '~icons/lucide/plus';
import Loader2Icon from '~icons/lucide/loader-2';
import AlertCircleIcon from '~icons/lucide/alert-circle';
import EditIcon from '~icons/lucide/edit';
import Trash2Icon from '~icons/lucide/trash-2';
import GlobeIcon from '~icons/lucide/globe';
export default {
name: 'JsiteDomainDNSRecords',
props: ['domain'],
data() {
return {
loading: false,
error: null,
dnsRecords: [],
pagination: {
pageno: 1,
limit: 20,
total: 0,
pagecount: 0
}
};
},
methods: {
// 获取DNS记录
async loadDNSRecords() {
if (!this.$domain.pg?.domain) {
this.error = '域名信息不存在';
return;
}
this.loading = true;
this.error = null;
try {
const request = createResource({
url: 'jcloud.api.domain_west.get_west_domain_dns_records',
params: {
domain: this.$domain.pg.domain,
limit: this.pagination.limit,
pageno: this.pagination.pageno
},
onSuccess: (response) => {
this.loading = false;
if (response.status === 'success' && response.data) {
this.dnsRecords = response.data.items || [];
this.pagination = {
pageno: response.data.pageno || 1,
limit: response.data.limit || 20,
total: response.data.total || 0,
pagecount: response.data.pagecount || 0
};
} else {
this.error = response.message || '获取DNS记录失败';
}
},
onError: (error) => {
this.loading = false;
this.error = getToastErrorMessage(error);
}
});
request.submit();
} catch (error) {
this.loading = false;
this.error = '获取DNS记录时发生错误';
console.error('加载DNS记录失败:', error);
}
},
// 刷新记录
refreshRecords() {
this.loadDNSRecords();
},
// 切换页面
changePage(page) {
if (page >= 1 && page <= this.pagination.pagecount) {
this.pagination.pageno = page;
this.loadDNSRecords();
}
},
// 获取记录类型样式
getRecordTypeVariant(type) {
const variantMap = {
'A': 'success',
'AAAA': 'info',
'CNAME': 'warning',
'MX': 'primary',
'TXT': 'secondary',
'NS': 'default'
};
return variantMap[type] || 'default';
},
// 显示添加记录对话框
showAddRecordDialog() {
const JsiteDomainAddDNSRecordDialog = defineAsyncComponent(() => import('./JsiteDomainAddDNSRecordDialog.vue'));
renderDialog(h(JsiteDomainAddDNSRecordDialog, {
domain: this.domain,
domainDoc: this.$domain.pg,
onSuccess: this.onRecordAdded
}));
},
// 编辑记录
editRecord(record) {
const JsiteDomainEditDNSRecordDialog = defineAsyncComponent(() => import('./JsiteDomainEditDNSRecordDialog.vue'));
renderDialog(h(JsiteDomainEditDNSRecordDialog, {
domain: this.domain,
domainDoc: this.$domain.pg,
record: record,
onSuccess: this.onRecordUpdated
}));
},
// 删除记录
deleteRecord(record) {
confirmDialog({
title: '删除DNS记录',
message: `确定要删除这条DNS记录吗\n类型: ${record.type}\n主机记录: ${record.item || record.host || '@'}\n记录值: ${record.value}`,
primaryAction: {
label: '删除',
variant: 'danger',
onClick: ({ hide }) => {
this.performDeleteRecord(record, hide);
}
}
});
},
// 执行删除记录
async performDeleteRecord(record, hide) {
try {
const request = createResource({
url: 'jcloud.api.domain_west.west_domain_delete_dns_record',
params: {
domain: this.$domain.pg.domain,
record_id: record.id
},
onSuccess: () => {
toast.success('DNS记录删除成功');
hide();
this.loadDNSRecords();
},
onError: (error) => {
toast.error(getToastErrorMessage(error));
}
});
request.submit();
} catch (error) {
toast.error('删除DNS记录失败');
console.error('删除DNS记录失败:', error);
}
},
// 记录添加成功回调
onRecordAdded() {
toast.success('DNS记录添加成功');
this.loadDNSRecords();
},
// 记录更新成功回调
onRecordUpdated() {
toast.success('DNS记录更新成功');
this.loadDNSRecords();
}
},
computed: {
$domain() {
return getCachedDocumentResource('Jsite Domain', this.domain);
}
},
mounted() {
this.loadDNSRecords();
}
};
</script>