record data validate done
This commit is contained in:
parent
5e2ae637a0
commit
9cc2696bbe
@ -25,7 +25,7 @@
|
||||
{{ t('common.cancel') }}
|
||||
</NButton>
|
||||
|
||||
<NButton size="small" type="error" :disabled="domain_name !== domain?.domain_name"
|
||||
<NButton size="small" type="error" :disabled="domain_name !== domain?.domain_name" attr-type="submit"
|
||||
:loading="loading" @click="confirm">
|
||||
<template #icon>
|
||||
<NIcon>
|
||||
|
@ -8,7 +8,8 @@
|
||||
|
||||
<NForm :model="record" inline :rules="rules">
|
||||
<NFormItem :label="t('records.recordType')">
|
||||
<NSelect v-model:value="record.record_type" :options="recordTypeOptions" @update:value="clearRecordContent"/>
|
||||
<NSelect v-model:value="record.record_type" :options="recordTypeOptions"
|
||||
@update:value="clearRecordContent" style="width: 8vw;" />
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('records.name')" path="name">
|
||||
<NInput v-model:value="record.name" />
|
||||
@ -21,44 +22,65 @@
|
||||
</NInputNumber>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<NForm :model="record" inline>
|
||||
<NForm :model="record" inline :rules="rules">
|
||||
<!-- A or AAAA -->
|
||||
<NFormItem :label="t('records.content')"
|
||||
v-if="[RecordTypes.RecordTypeA, RecordTypes.RecordTypeAAAA].indexOf(record.record_type) > -1">
|
||||
v-if="[RecordTypes.RecordTypeA, RecordTypes.RecordTypeAAAA].indexOf(record.record_type) > -1"
|
||||
path="ip">
|
||||
<NInput v-model:value="(record.content as ARecord | AAAARecord).ip" placeholder="IP" />
|
||||
</NFormItem>
|
||||
|
||||
<!-- CNAME or NS -->
|
||||
<NFormItem :label="t('records.content')"
|
||||
v-if="[RecordTypes.RecordTypeCNAME, RecordTypes.RecordTypeNS].indexOf(record.record_type) > -1">
|
||||
v-if="[RecordTypes.RecordTypeCNAME, RecordTypes.RecordTypeNS].indexOf(record.record_type) > -1"
|
||||
path="host">
|
||||
<NInput v-model:value="(record.content as CNAMERecord | NSRecord).host"
|
||||
:placeholder="t('records.form.host')" />
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeTXT === record.record_type">
|
||||
|
||||
<!-- TXT -->
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeTXT === record.record_type"
|
||||
path="txt">
|
||||
<NInput v-model:value="(record.content as TXTRecord).text" :placeholder="t('records.form.text')" />
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeMX === record.record_type">
|
||||
|
||||
<!-- MX -->
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeMX === record.record_type"
|
||||
path="mx">
|
||||
<NInputGroup>
|
||||
<NInput :placeholder="t('records.form.host')" v-model:value="(record.content as MXRecord).host"
|
||||
style="width: 75%;" />
|
||||
<NInputNumber :placeholder="t('records.form.preference')"
|
||||
v-model:value="(record.content as MXRecord).preference" style="width: 25%;" />
|
||||
v-model:value="(record.content as MXRecord).preference" style="width: 25%;"
|
||||
:show-button="false" />
|
||||
</NInputGroup>
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeSRV === record.record_type">
|
||||
|
||||
<!-- SRV -->
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeSRV === record.record_type"
|
||||
path="srv">
|
||||
<NInputGroup>
|
||||
<NInputNumber :placeholder="t('records.form.priority')"
|
||||
v-model:value="(record.content as SRVRecord).priority" style="width: 25%;" />
|
||||
v-model:value="(record.content as SRVRecord).priority" style="width: 15%;"
|
||||
:show-button="false" />
|
||||
<NInputNumber :placeholder="t('records.form.weight')"
|
||||
v-model:value="(record.content as SRVRecord).weight" style="width: 25%;" />
|
||||
v-model:value="(record.content as SRVRecord).weight" style="width: 15%;"
|
||||
:show-button="false" />
|
||||
<NInputNumber :placeholder="t('records.form.port')"
|
||||
v-model:value="(record.content as SRVRecord).port" style="width: 25%;" :min="0"
|
||||
:max="65535" />
|
||||
v-model:value="(record.content as SRVRecord).port" style="width: 15%;" :min="0" :max="65535"
|
||||
:show-button="false" />
|
||||
<NInput :placeholder="t('records.form.target')"
|
||||
v-model:value="(record.content as SRVRecord).target" style="width: 25%;" />
|
||||
v-model:value="(record.content as SRVRecord).target" style="width: 55%;" />
|
||||
</NInputGroup>
|
||||
</NFormItem>
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeCAA === record.record_type">
|
||||
|
||||
<!-- CAA -->
|
||||
<NFormItem :label="t('records.content')" v-if="RecordTypes.RecordTypeCAA === record.record_type"
|
||||
path="caa">
|
||||
<NInputGroup>
|
||||
<NInputNumber :placeholder="t('records.form.flag')"
|
||||
v-model:value="(record.content as CAARecord).flag" style="width: 20%;" />
|
||||
v-model:value="(record.content as CAARecord).flag" style="width: 20%;"
|
||||
:show-button="false" />
|
||||
<NInput :placeholder="t('records.form.tag')" v-model:value="(record.content as CAARecord).tag"
|
||||
style="width: 40%;" />
|
||||
<NInput :placeholder="t('records.form.value')"
|
||||
@ -78,7 +100,7 @@
|
||||
{{ t('common.cancel') }}
|
||||
</NButton>
|
||||
|
||||
<NButton size="small" type="primary" :loading="loading" @click="confirm" attr-type="submit">
|
||||
<NButton size="small" type="primary" :loading="loading" :disabled="invalidData !== (validationFlags.content | validationFlags.name)" @click="confirm" attr-type="submit">
|
||||
<template #icon>
|
||||
<NIcon>
|
||||
<Check />
|
||||
@ -104,9 +126,11 @@ import {
|
||||
NInputNumber,
|
||||
NInputGroup,
|
||||
NSelect,
|
||||
NIcon,
|
||||
useNotification,
|
||||
type FormRules,
|
||||
type SelectOption
|
||||
type SelectOption,
|
||||
type FormItemRule,
|
||||
} from 'naive-ui'
|
||||
import { getErrorInfo } from '@/apis/api';
|
||||
import {
|
||||
@ -126,12 +150,20 @@ import {
|
||||
import { Check, Times } from '@vicons/fa';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const enum validationFlags {
|
||||
name = 1,
|
||||
content = name << 1
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const props = defineProps<{
|
||||
record: Record,
|
||||
domain: string,
|
||||
}>()
|
||||
const emit = defineEmits(['reload-records'])
|
||||
|
||||
const invalidData = ref(validationFlags.content)
|
||||
const show = defineModel<boolean>('show', { default: false })
|
||||
const loading = ref(false)
|
||||
const notification = useNotification()
|
||||
@ -146,9 +178,138 @@ const recordTypeOptions = Object.entries(RecordTypes).filter(
|
||||
})
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: t('common.mandatory')
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.name
|
||||
if (!props.record.name || props.record.name === '') {
|
||||
invalidData.value &= ~validationFlags.name
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
txt : {
|
||||
trigger: 'blur' ,
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.content
|
||||
if (props.record.record_type !== RecordTypes.RecordTypeTXT) return true
|
||||
|
||||
const r = (props.record.content as TXTRecord)
|
||||
if (!r || !r.text || r.text === '') {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
host: {
|
||||
trigger: 'blur',
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.content
|
||||
if ([RecordTypes.RecordTypeCNAME, RecordTypes.RecordTypeNS].indexOf(props.record.record_type) === -1) return true
|
||||
|
||||
const r = (props.record.content as CNAMERecord | NSRecord)
|
||||
if (!r || !r.host || r.host === '') {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
|
||||
if (!r.host.endsWith('.')) {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('records.errors.endWithDot'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
ip: {
|
||||
trigger: 'blur',
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.content
|
||||
if ([RecordTypes.RecordTypeA, RecordTypes.RecordTypeAAAA].indexOf(props.record.record_type) === -1) return true
|
||||
const r = (props.record.content as AAAARecord | ARecord)
|
||||
if (!r || !r.ip || r.ip === '') {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
switch (props.record.record_type) {
|
||||
case RecordTypes.RecordTypeA:
|
||||
if (!/^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/.test(r.ip)) {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('records.errors.badIPv4'))
|
||||
}
|
||||
|
||||
break
|
||||
case RecordTypes.RecordTypeAAAA:
|
||||
if (!/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(r.ip)) {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('records.errors.badIPv6'))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
mx: {
|
||||
trigger: 'blur',
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.content
|
||||
if (props.record.record_type !== RecordTypes.RecordTypeMX) return true
|
||||
const r = (props.record.content as MXRecord)
|
||||
|
||||
if (!r || !r.host || !r.preference || r.host === '') {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
if (!r.host.endsWith('.')) {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('records.errors.endWithDot'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
srv: {
|
||||
trigger: 'blur',
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.content
|
||||
if (props.record.record_type !== RecordTypes.RecordTypeSRV) return true
|
||||
const r = (props.record.content as SRVRecord)
|
||||
|
||||
if (!r || !r.port || !r.priority || !r.weight || !r.target || r.target === '') {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
if (!r.target.endsWith('.')) {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('records.errors.endWithDot'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
caa: {
|
||||
trigger: 'blur',
|
||||
validator() {
|
||||
invalidData.value |= validationFlags.content
|
||||
if (props.record.record_type !== RecordTypes.RecordTypeCAA) return true
|
||||
const r = (props.record.content as CAARecord)
|
||||
|
||||
if (!r || !r.flag || !r.tag || r.tag === '' || !r.value || r.value === '') {
|
||||
invalidData.value &= ~validationFlags.content
|
||||
return new Error(t('common.mandatory'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
} as FormRules
|
||||
|
||||
@ -164,6 +325,7 @@ async function confirm() {
|
||||
} else {
|
||||
await recordStore.updateRecord(props.domain, props.record)
|
||||
}
|
||||
emit('reload-records')
|
||||
show.value = false
|
||||
} catch (e) {
|
||||
const msg = getErrorInfo(e)
|
||||
|
@ -3,7 +3,7 @@
|
||||
<NButtonGroup>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton size="tiny">
|
||||
<NButton size="tiny" @click="edit">
|
||||
<template #icon>
|
||||
<NIcon :component="EditRegular" />
|
||||
</template>
|
||||
@ -39,9 +39,13 @@ const props = defineProps<{
|
||||
domain: string
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['record-delete'])
|
||||
const emit = defineEmits(['record-delete', 'edit-record'])
|
||||
|
||||
function confirm() {
|
||||
emit('record-delete', props.domain, props.record)
|
||||
}
|
||||
|
||||
function edit() {
|
||||
emit('edit-record', props.domain, props.record)
|
||||
}
|
||||
</script>
|
@ -79,6 +79,12 @@ export default {
|
||||
flag: 'Flag',
|
||||
tag: 'Tag',
|
||||
value: 'Value'
|
||||
},
|
||||
|
||||
errors: {
|
||||
endWithDot: 'should end with a dot',
|
||||
badIPv4: 'invalid IPv4 address',
|
||||
badIPv6: 'invalid IPv6 address',
|
||||
}
|
||||
}
|
||||
}
|
@ -79,6 +79,12 @@ export default {
|
||||
flag: '标志',
|
||||
tag: '标签',
|
||||
value: '值'
|
||||
},
|
||||
|
||||
errors: {
|
||||
endWithDot: '应当以 . 结尾',
|
||||
badIPv4: '不是有效的 IPv4 地址',
|
||||
badIPv6: '不是有效的 IPv6 地址',
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import { getErrorInfo } from '@/apis/api'
|
||||
import { PlusSquare, RedoAlt, CheckCircle, Clock, Cogs, Search } from '@vicons/fa'
|
||||
import router from '@/router';
|
||||
import RecordOps from '@/components/records/RecordOps.vue'
|
||||
import RecordEditModal from '@/components/records/RecordEditModal.vue'
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -49,7 +50,7 @@ const columns = [
|
||||
{
|
||||
key: '',
|
||||
render(row: Record) {
|
||||
return <RecordOps record={row} domain={props.domain} onRecord-delete={deleteRecord} />
|
||||
return <RecordOps record={row} domain={props.domain} onRecord-delete={deleteRecord} onEdit-record={showEditing} />
|
||||
}
|
||||
}
|
||||
] as DataTableColumns<Record>
|
||||
@ -57,8 +58,10 @@ const columns = [
|
||||
const recordStore = useRecordStore()
|
||||
const notification = useNotification()
|
||||
|
||||
const records = ref<Record[] | undefined>([]);
|
||||
const records = ref<Record[] | undefined>([]as Record[]);
|
||||
const soa = ref<SOARecord>({} as SOARecord)
|
||||
const editModalShow = ref(false)
|
||||
const editingRecord = ref<Record>({} as Record)
|
||||
const loading = ref(true);
|
||||
onMounted(() => {
|
||||
try {
|
||||
@ -70,10 +73,13 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const reloadRecords = () => records.value = recordStore.records?.filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||
|
||||
|
||||
async function refreshRecords() {
|
||||
try {
|
||||
await recordStore.loadRecords(props.domain)
|
||||
records.value = recordStore.records?.filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||
reloadRecords()
|
||||
soa.value = recordStore.records?.find(e => e.record_type === RecordTypes.RecordTypeSOA)?.content as SOARecord
|
||||
} catch (err) {
|
||||
const msg = getErrorInfo(err)
|
||||
@ -102,24 +108,37 @@ function searchRecord(value: string) {
|
||||
async function deleteRecord(domain: string, record: Record) {
|
||||
try {
|
||||
await recordStore.removeRecord(domain, record)
|
||||
records.value = recordStore.records?.filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||
reloadRecords()
|
||||
} catch (err) {
|
||||
const msg = getErrorInfo(err)
|
||||
notification.error(msg)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
function showEditing(domain: string, record: Record) {
|
||||
editModalShow.value = true
|
||||
editingRecord.value = record
|
||||
}
|
||||
|
||||
function newRecord() {
|
||||
showEditing(props.domain, {
|
||||
zone: `${props.domain}.`,
|
||||
ttl: 500
|
||||
} as Record)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="records">
|
||||
<RecordEditModal v-model:show="editModalShow" :domain="domain" :record="editingRecord" @reload-records="reloadRecords"/>
|
||||
<NSpin size="large" v-if="loading" />
|
||||
<div v-else class="content">
|
||||
<NModalProvider>
|
||||
<NPageHeader :title="t('domains.dnsRecord')" :subtitle="domain" @back="goBack">
|
||||
<template #extra>
|
||||
<NFlex :wrap="false" justify="end" inline>
|
||||
<NButton type="primary">
|
||||
<NButton type="primary" @click="newRecord">
|
||||
<template #icon>
|
||||
<NIcon>
|
||||
<PlusSquare />
|
||||
|
Loading…
Reference in New Issue
Block a user