增加域名解析标签页
This commit is contained in:
parent
e723ada8fb
commit
c42115a0ad
246
dashboard/src2/components/JsiteDomainAddDNSRecordDialog.vue
Normal file
246
dashboard/src2/components/JsiteDomainAddDNSRecordDialog.vue
Normal file
@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<Dialog>
|
||||
<DialogContent class="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加DNS记录</DialogTitle>
|
||||
<DialogDescription>
|
||||
为域名 {{ domainDoc?.domain }} 添加新的DNS解析记录
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form @submit.prevent="submitForm" class="space-y-4">
|
||||
<!-- 记录类型 -->
|
||||
<div class="space-y-2">
|
||||
<Label for="record-type">记录类型</Label>
|
||||
<Select v-model="form.record_type" required>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择记录类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="A">A - IPv4地址</SelectItem>
|
||||
<SelectItem value="AAAA">AAAA - IPv6地址</SelectItem>
|
||||
<SelectItem value="CNAME">CNAME - 别名</SelectItem>
|
||||
<SelectItem value="MX">MX - 邮件交换</SelectItem>
|
||||
<SelectItem value="TXT">TXT - 文本记录</SelectItem>
|
||||
<SelectItem value="NS">NS - 域名服务器</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- 主机记录 -->
|
||||
<div class="space-y-2">
|
||||
<Label for="host">主机记录</Label>
|
||||
<Input
|
||||
id="host"
|
||||
v-model="form.host"
|
||||
placeholder="例如: www 或 @ (留空表示根域名)"
|
||||
required
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
@ 表示根域名,www 表示 www.yourdomain.com
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 记录值 -->
|
||||
<div class="space-y-2">
|
||||
<Label for="value">记录值</Label>
|
||||
<Input
|
||||
id="value"
|
||||
v-model="form.value"
|
||||
:placeholder="getValuePlaceholder()"
|
||||
required
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ getValueDescription() }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- TTL -->
|
||||
<div class="space-y-2">
|
||||
<Label for="ttl">TTL (秒)</Label>
|
||||
<Input
|
||||
id="ttl"
|
||||
v-model.number="form.ttl"
|
||||
type="number"
|
||||
min="60"
|
||||
max="86400"
|
||||
placeholder="600"
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
建议值:600秒 (10分钟) 到 86400秒 (24小时)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- MX优先级 (仅MX记录显示) -->
|
||||
<div v-if="form.record_type === 'MX'" class="space-y-2">
|
||||
<Label for="mx-priority">MX优先级</Label>
|
||||
<Input
|
||||
id="mx-priority"
|
||||
v-model.number="form.mx_priority"
|
||||
type="number"
|
||||
min="0"
|
||||
max="65535"
|
||||
placeholder="10"
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
数值越小优先级越高,建议值:10
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button @click="$emit('close')" variant="outline">
|
||||
取消
|
||||
</Button>
|
||||
<Button @click="submitForm" :loading="loading" variant="solid">
|
||||
添加记录
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
createResource
|
||||
} from 'jingrow-ui';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { getToastErrorMessage } from '../utils/toast';
|
||||
|
||||
export default {
|
||||
name: 'JsiteDomainAddDNSRecordDialog',
|
||||
props: ['domain', 'domainDoc', 'onSuccess'],
|
||||
emits: ['close'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
record_type: 'A',
|
||||
host: '',
|
||||
value: '',
|
||||
ttl: 600,
|
||||
mx_priority: 10
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 获取记录值占位符
|
||||
getValuePlaceholder() {
|
||||
const placeholders = {
|
||||
'A': '例如: 192.168.1.1',
|
||||
'AAAA': '例如: 2001:db8::1',
|
||||
'CNAME': '例如: example.com',
|
||||
'MX': '例如: mail.example.com',
|
||||
'TXT': '例如: 验证字符串',
|
||||
'NS': '例如: ns1.example.com'
|
||||
};
|
||||
return placeholders[this.form.record_type] || '';
|
||||
},
|
||||
|
||||
// 获取记录值描述
|
||||
getValueDescription() {
|
||||
const descriptions = {
|
||||
'A': '输入IPv4地址,如:192.168.1.1',
|
||||
'AAAA': '输入IPv6地址,如:2001:db8::1',
|
||||
'CNAME': '输入目标域名,如:example.com',
|
||||
'MX': '输入邮件服务器域名,如:mail.example.com',
|
||||
'TXT': '输入文本内容,常用于域名验证',
|
||||
'NS': '输入域名服务器地址,如:ns1.example.com'
|
||||
};
|
||||
return descriptions[this.form.record_type] || '';
|
||||
},
|
||||
|
||||
// 验证表单
|
||||
validateForm() {
|
||||
if (!this.form.record_type) {
|
||||
toast.error('请选择记录类型');
|
||||
return false;
|
||||
}
|
||||
if (!this.form.value) {
|
||||
toast.error('请输入记录值');
|
||||
return false;
|
||||
}
|
||||
if (this.form.ttl < 60 || this.form.ttl > 86400) {
|
||||
toast.error('TTL值必须在60-86400秒之间');
|
||||
return false;
|
||||
}
|
||||
if (this.form.record_type === 'MX' && (this.form.mx_priority < 0 || this.form.mx_priority > 65535)) {
|
||||
toast.error('MX优先级必须在0-65535之间');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
async submitForm() {
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
// 构建记录数据
|
||||
const recordData = {
|
||||
type: this.form.record_type,
|
||||
host: this.form.host || '@',
|
||||
value: this.form.value,
|
||||
ttl: this.form.ttl
|
||||
};
|
||||
|
||||
// 如果是MX记录,添加优先级
|
||||
if (this.form.record_type === 'MX') {
|
||||
recordData.level = this.form.mx_priority;
|
||||
}
|
||||
|
||||
const request = createResource({
|
||||
url: 'jcloud.api.domain_west.west_domain_add_dns_record',
|
||||
params: {
|
||||
domain: this.domainDoc.domain,
|
||||
record_type: this.form.record_type,
|
||||
host: this.form.host || '@',
|
||||
value: this.form.value,
|
||||
ttl: this.form.ttl,
|
||||
level: this.form.mx_priority
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
this.loading = false;
|
||||
if (response.status === 'success') {
|
||||
toast.success('DNS记录添加成功');
|
||||
this.$emit('close');
|
||||
if (this.onSuccess) {
|
||||
this.onSuccess();
|
||||
}
|
||||
} else {
|
||||
toast.error(response.message || '添加DNS记录失败');
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
this.loading = false;
|
||||
toast.error(getToastErrorMessage(error));
|
||||
}
|
||||
});
|
||||
request.submit();
|
||||
} catch (error) {
|
||||
this.loading = false;
|
||||
toast.error('添加DNS记录失败');
|
||||
console.error('添加DNS记录失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
347
dashboard/src2/components/JsiteDomainDNSRecords.vue
Normal file
347
dashboard/src2/components/JsiteDomainDNSRecords.vue
Normal file
@ -0,0 +1,347 @@
|
||||
<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-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
类型
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
主机记录
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
记录值
|
||||
</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
TTL
|
||||
</th>
|
||||
<th class="px-6 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-6 py-4 whitespace-nowrap">
|
||||
<Badge :variant="getRecordTypeVariant(record.type)">
|
||||
{{ record.type }}
|
||||
</Badge>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900">
|
||||
{{ record.item || record.host || '@' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900">
|
||||
{{ record.value }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ record.ttl || '600' }}
|
||||
<span v-if="record.type === 'MX' && record.level" class="ml-2 text-xs text-blue-600">
|
||||
(优先级: {{ record.level }})
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Button
|
||||
@click="editRecord(record)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
>
|
||||
<EditIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
@click="deleteRecord(record)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="text-red-600 hover:text-red-700"
|
||||
>
|
||||
<Trash2Icon class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
259
dashboard/src2/components/JsiteDomainEditDNSRecordDialog.vue
Normal file
259
dashboard/src2/components/JsiteDomainEditDNSRecordDialog.vue
Normal file
@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<Dialog>
|
||||
<DialogContent class="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>编辑DNS记录</DialogTitle>
|
||||
<DialogDescription>
|
||||
编辑域名 {{ domainDoc?.domain }} 的DNS解析记录
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form @submit.prevent="submitForm" class="space-y-4">
|
||||
<!-- 记录类型 -->
|
||||
<div class="space-y-2">
|
||||
<Label for="record-type">记录类型</Label>
|
||||
<Select v-model="form.record_type" required>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择记录类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="A">A - IPv4地址</SelectItem>
|
||||
<SelectItem value="AAAA">AAAA - IPv6地址</SelectItem>
|
||||
<SelectItem value="CNAME">CNAME - 别名</SelectItem>
|
||||
<SelectItem value="MX">MX - 邮件交换</SelectItem>
|
||||
<SelectItem value="TXT">TXT - 文本记录</SelectItem>
|
||||
<SelectItem value="NS">NS - 域名服务器</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- 主机记录 -->
|
||||
<div class="space-y-2">
|
||||
<Label for="host">主机记录</Label>
|
||||
<Input
|
||||
id="host"
|
||||
v-model="form.host"
|
||||
placeholder="例如: www 或 @ (留空表示根域名)"
|
||||
required
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
@ 表示根域名,www 表示 www.yourdomain.com
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 记录值 -->
|
||||
<div class="space-y-2">
|
||||
<Label for="value">记录值</Label>
|
||||
<Input
|
||||
id="value"
|
||||
v-model="form.value"
|
||||
:placeholder="getValuePlaceholder()"
|
||||
required
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ getValueDescription() }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- TTL -->
|
||||
<div class="space-y-2">
|
||||
<Label for="ttl">TTL (秒)</Label>
|
||||
<Input
|
||||
id="ttl"
|
||||
v-model.number="form.ttl"
|
||||
type="number"
|
||||
min="60"
|
||||
max="86400"
|
||||
placeholder="600"
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
建议值:600秒 (10分钟) 到 86400秒 (24小时)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- MX优先级 (仅MX记录显示) -->
|
||||
<div v-if="form.record_type === 'MX'" class="space-y-2">
|
||||
<Label for="mx-priority">MX优先级</Label>
|
||||
<Input
|
||||
id="mx-priority"
|
||||
v-model.number="form.mx_priority"
|
||||
type="number"
|
||||
min="0"
|
||||
max="65535"
|
||||
placeholder="10"
|
||||
/>
|
||||
<p class="text-xs text-gray-500">
|
||||
数值越小优先级越高,建议值:10
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button @click="$emit('close')" variant="outline">
|
||||
取消
|
||||
</Button>
|
||||
<Button @click="submitForm" :loading="loading" variant="solid">
|
||||
保存修改
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
createResource
|
||||
} from 'jingrow-ui';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { getToastErrorMessage } from '../utils/toast';
|
||||
|
||||
export default {
|
||||
name: 'JsiteDomainEditDNSRecordDialog',
|
||||
props: ['domain', 'domainDoc', 'record', 'onSuccess'],
|
||||
emits: ['close'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
record_type: 'A',
|
||||
host: '',
|
||||
value: '',
|
||||
ttl: 600,
|
||||
mx_priority: 10
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 初始化表单数据
|
||||
initForm() {
|
||||
if (this.record) {
|
||||
this.form = {
|
||||
record_type: this.record.type || 'A',
|
||||
host: this.record.item || this.record.host || '',
|
||||
value: this.record.value || '',
|
||||
ttl: this.record.ttl || 600,
|
||||
mx_priority: this.record.level || this.record.mx_priority || 10
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 获取记录值占位符
|
||||
getValuePlaceholder() {
|
||||
const placeholders = {
|
||||
'A': '例如: 192.168.1.1',
|
||||
'AAAA': '例如: 2001:db8::1',
|
||||
'CNAME': '例如: example.com',
|
||||
'MX': '例如: mail.example.com',
|
||||
'TXT': '例如: 验证字符串',
|
||||
'NS': '例如: ns1.example.com'
|
||||
};
|
||||
return placeholders[this.form.record_type] || '';
|
||||
},
|
||||
|
||||
// 获取记录值描述
|
||||
getValueDescription() {
|
||||
const descriptions = {
|
||||
'A': '输入IPv4地址,如:192.168.1.1',
|
||||
'AAAA': '输入IPv6地址,如:2001:db8::1',
|
||||
'CNAME': '输入目标域名,如:example.com',
|
||||
'MX': '输入邮件服务器域名,如:mail.example.com',
|
||||
'TXT': '输入文本内容,常用于域名验证',
|
||||
'NS': '输入域名服务器地址,如:ns1.example.com'
|
||||
};
|
||||
return descriptions[this.form.record_type] || '';
|
||||
},
|
||||
|
||||
// 验证表单
|
||||
validateForm() {
|
||||
if (!this.form.record_type) {
|
||||
toast.error('请选择记录类型');
|
||||
return false;
|
||||
}
|
||||
if (!this.form.value) {
|
||||
toast.error('请输入记录值');
|
||||
return false;
|
||||
}
|
||||
if (this.form.ttl < 60 || this.form.ttl > 86400) {
|
||||
toast.error('TTL值必须在60-86400秒之间');
|
||||
return false;
|
||||
}
|
||||
if (this.form.record_type === 'MX' && (this.form.mx_priority < 0 || this.form.mx_priority > 65535)) {
|
||||
toast.error('MX优先级必须在0-65535之间');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
async submitForm() {
|
||||
if (!this.validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
// 构建记录数据
|
||||
const recordData = {
|
||||
id: this.record.id,
|
||||
type: this.form.record_type,
|
||||
host: this.form.host || '@',
|
||||
value: this.form.value,
|
||||
ttl: this.form.ttl
|
||||
};
|
||||
|
||||
// 如果是MX记录,添加优先级
|
||||
if (this.form.record_type === 'MX') {
|
||||
recordData.level = this.form.mx_priority;
|
||||
}
|
||||
|
||||
const request = createResource({
|
||||
url: 'jcloud.api.domain_west.west_domain_modify_dns',
|
||||
params: {
|
||||
domain: this.domainDoc.domain,
|
||||
records: [recordData]
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
this.loading = false;
|
||||
if (response.status === 'success') {
|
||||
toast.success('DNS记录更新成功');
|
||||
this.$emit('close');
|
||||
if (this.onSuccess) {
|
||||
this.onSuccess();
|
||||
}
|
||||
} else {
|
||||
toast.error(response.message || '更新DNS记录失败');
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
this.loading = false;
|
||||
toast.error(getToastErrorMessage(error));
|
||||
}
|
||||
});
|
||||
request.submit();
|
||||
} catch (error) {
|
||||
this.loading = false;
|
||||
toast.error('更新DNS记录失败');
|
||||
console.error('更新DNS记录失败:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initForm();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -174,6 +174,15 @@ export default {
|
||||
props: domain => {
|
||||
return { domain: domain.pg?.name };
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '域名解析',
|
||||
route: 'dns',
|
||||
type: 'Component',
|
||||
component: defineAsyncComponent(() => import('../components/JsiteDomainDNSRecords.vue')),
|
||||
props: domain => {
|
||||
return { domain: domain.pg?.name };
|
||||
}
|
||||
}
|
||||
],
|
||||
fields: [
|
||||
|
||||
@ -312,7 +312,7 @@ class WestDomain:
|
||||
return self._make_request('/domain/?act=modifydns', 'POST', body_params=body_params)
|
||||
|
||||
def add_dns_record(self, domain: str, record_type: str,
|
||||
host: str, value: str, ttl: int = 600) -> Dict[str, Any]:
|
||||
host: str, value: str, ttl: int = 600, level: int = 10) -> Dict[str, Any]:
|
||||
"""
|
||||
添加DNS记录
|
||||
|
||||
@ -322,6 +322,7 @@ class WestDomain:
|
||||
host: 主机记录
|
||||
value: 记录值
|
||||
ttl: TTL值
|
||||
level: 优先级(MX记录使用)
|
||||
"""
|
||||
record = {
|
||||
'type': record_type,
|
||||
@ -329,6 +330,11 @@ class WestDomain:
|
||||
'value': value,
|
||||
'ttl': ttl,
|
||||
}
|
||||
|
||||
# 如果是MX记录,添加优先级
|
||||
if record_type == 'MX':
|
||||
record['level'] = level
|
||||
|
||||
return self.modify_dns_records(domain, [record])
|
||||
|
||||
def delete_dns_record(self, domain: str, record_id: str) -> Dict[str, Any]:
|
||||
@ -829,7 +835,23 @@ def west_domain_modify_dns(**data):
|
||||
if not records:
|
||||
return {"status": "error", "message": "缺少DNS记录参数"}
|
||||
|
||||
return client.modify_dns_records(domain, records)
|
||||
# 处理记录数据,确保MX记录有正确的优先级字段
|
||||
processed_records = []
|
||||
for record in records:
|
||||
processed_record = {
|
||||
'type': record.get('type'),
|
||||
'host': record.get('host'),
|
||||
'value': record.get('value'),
|
||||
'ttl': record.get('ttl', 600)
|
||||
}
|
||||
|
||||
# 如果是MX记录,添加优先级
|
||||
if record.get('type') == 'MX':
|
||||
processed_record['level'] = record.get('level', 10)
|
||||
|
||||
processed_records.append(processed_record)
|
||||
|
||||
return client.modify_dns_records(domain, processed_records)
|
||||
|
||||
|
||||
@jingrow.whitelist()
|
||||
@ -844,11 +866,12 @@ def west_domain_add_dns_record(**data):
|
||||
host = data.get('host')
|
||||
value = data.get('value')
|
||||
ttl = data.get('ttl', 600)
|
||||
level = data.get('level', 10)
|
||||
|
||||
if not all([domain, record_type, host, value]):
|
||||
return {"status": "error", "message": "缺少必要参数"}
|
||||
|
||||
return client.add_dns_record(domain, record_type, host, value, ttl)
|
||||
return client.add_dns_record(domain, record_type, host, value, ttl, level)
|
||||
|
||||
|
||||
@jingrow.whitelist()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user