all tsx used, no vue SFC
This commit is contained in:
parent
731504ae82
commit
01765c4e7f
32
web/src/App.tsx
Normal file
32
web/src/App.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
NNotificationProvider,
|
||||||
|
NConfigProvider,
|
||||||
|
NGlobalStyle,
|
||||||
|
useOsTheme,
|
||||||
|
darkTheme,
|
||||||
|
lightTheme,
|
||||||
|
} from "naive-ui";
|
||||||
|
|
||||||
|
import { zhCN, dateZhCN, enUS, dateEnUS } from 'naive-ui'
|
||||||
|
import { RouterView } from "vue-router";
|
||||||
|
|
||||||
|
const osThemeRef = useOsTheme()
|
||||||
|
const theme = osThemeRef.value === 'dark' ? darkTheme : lightTheme
|
||||||
|
const locale = navigator.language === "zh-CN" ? zhCN : enUS
|
||||||
|
const dateLocale = navigator.language === "zh-CN" ? dateZhCN : dateEnUS
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
document.title = 'reCoreD-UI'
|
||||||
|
return (
|
||||||
|
<NConfigProvider theme={theme} locale={locale} date-locale={dateLocale}>
|
||||||
|
<NGlobalStyle />
|
||||||
|
<NNotificationProvider max={3}>
|
||||||
|
<RouterView />
|
||||||
|
</NNotificationProvider>
|
||||||
|
</NConfigProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
App.displayName = 'App'
|
||||||
|
|
||||||
|
export default App
|
@ -1,32 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
NNotificationProvider,
|
|
||||||
NConfigProvider,
|
|
||||||
NGlobalStyle,
|
|
||||||
useOsTheme,
|
|
||||||
darkTheme,
|
|
||||||
lightTheme,
|
|
||||||
type GlobalTheme
|
|
||||||
} from "naive-ui";
|
|
||||||
import { zhCN, dateZhCN, enUS, dateEnUS, type NLocale, type NDateLocale } from 'naive-ui'
|
|
||||||
import { RouterView } from "vue-router";
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
|
|
||||||
const osThemeRef = useOsTheme()
|
|
||||||
const theme = osThemeRef.value === 'dark' ? darkTheme : lightTheme
|
|
||||||
const locale = navigator.language === "zh-CN" ? zhCN : enUS
|
|
||||||
const dateLocale = navigator.language === "zh-CN" ? dateZhCN : dateEnUS
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.title = 'reCoreD-UI'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NConfigProvider :theme="theme" :locale="locale" :date-locale="dateLocale">
|
|
||||||
<NGlobalStyle />
|
|
||||||
<NNotificationProvider :max="3">
|
|
||||||
<RouterView />
|
|
||||||
</NNotificationProvider>
|
|
||||||
</NConfigProvider>
|
|
||||||
</template>
|
|
@ -76,4 +76,15 @@ function DomainOps({ domain }: Props, { emit }: SetupContext<Events>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DomainOps.props = {
|
||||||
|
domain: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainOps.emits = {
|
||||||
|
removeDomain: (d:Domain) => d,
|
||||||
|
editDomain: (d:Domain) => d
|
||||||
|
} as Events
|
||||||
|
|
||||||
export default DomainOps
|
export default DomainOps
|
459
web/src/components/records/RecordEditModal.tsx
Normal file
459
web/src/components/records/RecordEditModal.tsx
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
import {
|
||||||
|
NModal,
|
||||||
|
NCard,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NFlex,
|
||||||
|
NButton,
|
||||||
|
NInput,
|
||||||
|
NInputNumber,
|
||||||
|
NInputGroup,
|
||||||
|
NSelect,
|
||||||
|
NIcon,
|
||||||
|
type FormRules,
|
||||||
|
type SelectOption,
|
||||||
|
createDiscreteApi,
|
||||||
|
type FormItemRule,
|
||||||
|
} from 'naive-ui'
|
||||||
|
import { getErrorInfo } from '@/apis/api';
|
||||||
|
import {
|
||||||
|
useRecordStore,
|
||||||
|
RecordTypes,
|
||||||
|
type Record,
|
||||||
|
type ARecord,
|
||||||
|
type AAAARecord,
|
||||||
|
type CAARecord,
|
||||||
|
type CNAMERecord,
|
||||||
|
type NSRecord,
|
||||||
|
type SRVRecord,
|
||||||
|
type TXTRecord,
|
||||||
|
type MXRecord,
|
||||||
|
type RecordT,
|
||||||
|
} from '@/stores/records';
|
||||||
|
import { Check, Times } from '@vicons/fa';
|
||||||
|
import { ref, type SetupContext } from 'vue';
|
||||||
|
import i18n from '@/locale/i18n';
|
||||||
|
|
||||||
|
const { t } = i18n.global
|
||||||
|
|
||||||
|
const enum validationFlags {
|
||||||
|
name = 1,
|
||||||
|
content = name << 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
record: Record
|
||||||
|
domain: string
|
||||||
|
show: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Events = {
|
||||||
|
reloadRecords(): void
|
||||||
|
'update:show': (v: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidData = ref(validationFlags.content)
|
||||||
|
const loading = ref(false)
|
||||||
|
const recordStore = useRecordStore()
|
||||||
|
const { notification } = createDiscreteApi(['notification'])
|
||||||
|
const recordTypeOptions = Object.entries(RecordTypes).filter(
|
||||||
|
e => e[1] !== RecordTypes.RecordTypeSOA
|
||||||
|
).map(e => {
|
||||||
|
return {
|
||||||
|
label: e[1],
|
||||||
|
value: e[1]
|
||||||
|
} as SelectOption
|
||||||
|
})
|
||||||
|
|
||||||
|
function validateName(_rule: FormItemRule, value: string): boolean | Error {
|
||||||
|
invalidData.value |= validationFlags.name
|
||||||
|
if (!value || value === '') {
|
||||||
|
invalidData.value &= ~validationFlags.name
|
||||||
|
return new Error(t('common.mandatory'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.includes(' ')) {
|
||||||
|
invalidData.value &= ~validationFlags.name
|
||||||
|
return new Error(t('records.errors.hasSpace'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.startsWith('.') || value.endsWith('.')) {
|
||||||
|
invalidData.value &= ~validationFlags.name
|
||||||
|
return new Error(t('records.errors.badName.dotAndMinus'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.startsWith('-') || value.endsWith('-')) {
|
||||||
|
invalidData.value &= ~validationFlags.name
|
||||||
|
return new Error(t('records.errors.badName.dotAndMinus'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.includes('..')) {
|
||||||
|
invalidData.value &= ~validationFlags.name
|
||||||
|
return new Error(t('records.errors.badName.doubleDots'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.split('.').filter(e => e.length > 63).length > 0) {
|
||||||
|
invalidData.value &= ~validationFlags.name
|
||||||
|
return new Error(t('records.errors.badName.longerThan63'))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateTXTRecord(record: Record) {
|
||||||
|
return () => {
|
||||||
|
invalidData.value |= validationFlags.content
|
||||||
|
if (record.record_type !== RecordTypes.RecordTypeTXT) return true
|
||||||
|
|
||||||
|
const r = (record.content as TXTRecord)
|
||||||
|
if (!r || !r.text || r.text === '') {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('common.mandatory'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateHostRecord(record: Record) {
|
||||||
|
return () => {
|
||||||
|
invalidData.value |= validationFlags.content
|
||||||
|
if ([RecordTypes.RecordTypeCNAME, RecordTypes.RecordTypeNS].indexOf(record.record_type) === -1) return true
|
||||||
|
|
||||||
|
const r = (record.content as CNAMERecord | NSRecord)
|
||||||
|
if (!r || !r.host || r.host === '') {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('common.mandatory'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.host.includes(' ')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.hasSpace'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.host.endsWith('.')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.endWithDot'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateIPRecord(record: Record) {
|
||||||
|
return () => {
|
||||||
|
invalidData.value |= validationFlags.content
|
||||||
|
if ([RecordTypes.RecordTypeA, RecordTypes.RecordTypeAAAA].indexOf(record.record_type) === -1) return true
|
||||||
|
const r = (record.content as AAAARecord | ARecord)
|
||||||
|
if (!r || !r.ip || r.ip === '') {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('common.mandatory'))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMXRecord(record: Record) {
|
||||||
|
return () => {
|
||||||
|
invalidData.value |= validationFlags.content
|
||||||
|
if (record.record_type !== RecordTypes.RecordTypeMX) return true
|
||||||
|
const r = (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.includes(' ')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.hasSpace'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.host.endsWith('.')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.endWithDot'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSRVRecord(record: Record) {
|
||||||
|
return () => {
|
||||||
|
invalidData.value |= validationFlags.content
|
||||||
|
if (record.record_type !== RecordTypes.RecordTypeSRV) return true
|
||||||
|
const r = (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.includes(' ')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.hasSpace'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.target.endsWith('.')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.endWithDot'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCAARecord(record: Record) {
|
||||||
|
return () => {
|
||||||
|
invalidData.value |= validationFlags.content
|
||||||
|
if (record.record_type !== RecordTypes.RecordTypeCAA) return true
|
||||||
|
const r = (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'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.tag.includes(' ')) {
|
||||||
|
invalidData.value &= ~validationFlags.content
|
||||||
|
return new Error(t('records.errors.hasSpace'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRules(record: Record): FormRules {
|
||||||
|
return {
|
||||||
|
name: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateName
|
||||||
|
},
|
||||||
|
txt: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateTXTRecord(record)
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateHostRecord(record)
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateIPRecord(record)
|
||||||
|
},
|
||||||
|
mx: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateMXRecord(record)
|
||||||
|
},
|
||||||
|
srv: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateSRVRecord(record)
|
||||||
|
},
|
||||||
|
caa: {
|
||||||
|
trigger: 'blur',
|
||||||
|
validator: validateCAARecord(record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirm({ record, domain }: Props) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
if (!record.id || record.id < 1) {
|
||||||
|
await recordStore.addRecord(domain, record)
|
||||||
|
} else {
|
||||||
|
await recordStore.updateRecord(domain, record)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const msg = getErrorInfo(e)
|
||||||
|
notification.error(msg)
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function modalHeader({ record }: Props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(!record || !record.id || record.id < 1) ? <span>{t('common.new')}</span> : <span> t('common.edit')</span>}
|
||||||
|
<span>{t('records._')}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function modalActions({ record, domain }: Props, { emit }: SetupContext<Events>) {
|
||||||
|
return (
|
||||||
|
<NFlex justify='end'>
|
||||||
|
<NButton size='small' onClick={() => emit('update:show', false)}>
|
||||||
|
{{
|
||||||
|
icon: () => <NIcon component={Times} />,
|
||||||
|
default: () => t('common.cancel')
|
||||||
|
}}
|
||||||
|
</NButton>
|
||||||
|
|
||||||
|
<NButton size='small' type='primary' loading={loading.value} attrType='submit'
|
||||||
|
disabled={invalidData.value !== (validationFlags.content | validationFlags.name)}
|
||||||
|
onClick={() => confirm({ record, domain, show: false }).then(() => { emit('reloadRecords'); emit('update:show', false) })}>
|
||||||
|
{{
|
||||||
|
icon: () => <NIcon component={Check} />,
|
||||||
|
default: () => t('common.confirm')
|
||||||
|
}}
|
||||||
|
</NButton>
|
||||||
|
</NFlex>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function modalBody({ record }: Props) {
|
||||||
|
const rules = buildRules(record)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NForm model={record} rules={rules} inline>
|
||||||
|
<NFormItem label={t('records.recordType')}>
|
||||||
|
<NSelect value={record.record_type}
|
||||||
|
onUpdate:value={(v) => { record.record_type = v; record.content = {} as RecordT }}
|
||||||
|
options={recordTypeOptions} style={{ width: '8vw' }} />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label={t('records.name')} path='name'>
|
||||||
|
<NInput value={record.name} onUpdate:value={v => record.name = v} />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem label='TTL' path='ttl'>
|
||||||
|
<NInputNumber value={record.ttl} onUpdate:value={v => v ? record.ttl = v : null} showButton={false} >
|
||||||
|
{{
|
||||||
|
suffix: () => t('common.unitForSecond')
|
||||||
|
}}
|
||||||
|
</NInputNumber>
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
<NForm model={record} rules={rules}>
|
||||||
|
<modalBodyContent type={record.record_type} record={record} />
|
||||||
|
</NForm>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const IPRecordE = ({ record }: Props) => (
|
||||||
|
<NFormItem label={t('records.content')} path='ip'>
|
||||||
|
<NInput value={(record.content as ARecord | AAAARecord).ip} onUpdate:value={v => (record.content as ARecord | AAAARecord).ip = v} placeholder='IP' />
|
||||||
|
</NFormItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
const HostRecordE = ({ record }: Props) => (
|
||||||
|
<NFormItem label={t('records.content')} path='host'>
|
||||||
|
<NInput value={(record.content as CNAMERecord | NSRecord).host} onUpdate:value={v => (record.content as CNAMERecord | NSRecord).host = v} placeholder={t('records.form.host')} />
|
||||||
|
</NFormItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
const TXTRecordE = ({ record }: Props) => (
|
||||||
|
<NFormItem label={t('records.content')} path='txt'>
|
||||||
|
<NInput value={(record.content as TXTRecord).text} onUpdateValue={v => (record.content as TXTRecord).text = v} placeholder={t('records.form.text')} />
|
||||||
|
</NFormItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
const MXRecordE = ({ record }: Props) => (
|
||||||
|
<NFormItem label={t('records.content')} path='mx'>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInput placeholder={t('records.form.host')}
|
||||||
|
value={(record.content as MXRecord).host}
|
||||||
|
onUpdate:value={v => (record.content as MXRecord).host = v}
|
||||||
|
style={{ width: '75%' }} />
|
||||||
|
<NInputNumber placeholder={t('records.form.preference')}
|
||||||
|
value={(record.content as MXRecord).preference}
|
||||||
|
onUpdate:value={v => v ? (record.content as MXRecord).preference = v : null}
|
||||||
|
style={{ width: '25%' }} show-button={false} />
|
||||||
|
</NInputGroup>
|
||||||
|
</NFormItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
const CAARecordE = ({ record }: Props) => (
|
||||||
|
<NFormItem label={t('records.content')} path='caa'>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputNumber placeholder={t('records.form.flag')}
|
||||||
|
value={(record.content as CAARecord).flag} style={{ width: '20%' }}
|
||||||
|
onUpdate:value={v => v ? (record.content as CAARecord).flag = v : null}
|
||||||
|
show-button={false} />
|
||||||
|
<NInput placeholder={t('records.form.tag')}
|
||||||
|
value={(record.content as CAARecord).tag}
|
||||||
|
onUpdate:value={v => v ? (record.content as CAARecord).tag = v : null}
|
||||||
|
style={{ width: '40%' }} />
|
||||||
|
<NInput placeholder={t('records.form.value')}
|
||||||
|
value={(record.content as CAARecord).value}
|
||||||
|
onUpdate:value={v => v ? (record.content as CAARecord).value = v : null}
|
||||||
|
style={{ width: '40%' }} />
|
||||||
|
</NInputGroup>
|
||||||
|
</NFormItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
const SRVRecordE = ({ record }: Props) => (
|
||||||
|
<NFormItem label={t('records.content')} path='srv'>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputNumber placeholder={t('records.form.priority')}
|
||||||
|
value={(record.content as SRVRecord).priority} style={{ width: '15%' }}
|
||||||
|
onUpdateValue={v => v ? (record.content as SRVRecord).priority = v : null}
|
||||||
|
show-button={false} />
|
||||||
|
<NInputNumber placeholder={t('records.form.weight')}
|
||||||
|
value={(record.content as SRVRecord).weight} style={{ width: '15%' }}
|
||||||
|
onUpdate:value={v => v ? (record.content as SRVRecord).weight = v : null}
|
||||||
|
show-button={false} />
|
||||||
|
<NInputNumber placeholder={t('records.form.port')}
|
||||||
|
value={(record.content as SRVRecord).port} style={{ width: '15%' }} min={0} max={65535}
|
||||||
|
onUpdate:value={v => v ? (record.content as SRVRecord).port = v : null}
|
||||||
|
show-button={false} />
|
||||||
|
<NInput placeholder={t('records.form.target')}
|
||||||
|
value={(record.content as SRVRecord).target} style={{ width: '55%' }}
|
||||||
|
onUpdate:value={v => (record.content as SRVRecord).target = v}
|
||||||
|
/>
|
||||||
|
</NInputGroup>
|
||||||
|
</NFormItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
const modalBodyContent = ({ type, record }: { type: RecordTypes, record: Record }) => {
|
||||||
|
const e = {
|
||||||
|
'A': IPRecordE,
|
||||||
|
'AAAA': IPRecordE,
|
||||||
|
'CNAME': HostRecordE,
|
||||||
|
'NS': HostRecordE,
|
||||||
|
'TXT': TXTRecordE,
|
||||||
|
'MX': MXRecordE,
|
||||||
|
'SRV': SRVRecordE,
|
||||||
|
'CAA': CAARecordE,
|
||||||
|
'SOA': ({ }: Props) => <></>
|
||||||
|
}[type]
|
||||||
|
return <e record={record} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function RecordEditModal(
|
||||||
|
{ domain, show, record }: Props,
|
||||||
|
{ emit }: SetupContext<Events>) {
|
||||||
|
return (
|
||||||
|
<NModal maskClosable={false} show={show}>
|
||||||
|
<NCard style={{ width: '640px' }} role='dialog'>
|
||||||
|
{{
|
||||||
|
header: () => <modalHeader record={record} />,
|
||||||
|
default: () => <modalBody record={record} />,
|
||||||
|
action: () => <modalActions record={record} domain={domain}
|
||||||
|
onUpdate:show={(v: boolean) => emit('update:show', v)}
|
||||||
|
onReloadRecords={() => emit('reloadRecords')} />
|
||||||
|
}}
|
||||||
|
</NCard>
|
||||||
|
</NModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RecordEditModal
|
@ -1,380 +0,0 @@
|
|||||||
<template>
|
|
||||||
<NModal :mask-closable="false" :show="show">
|
|
||||||
<NCard style="width: 640px" role="dialog" aria-modal="true">
|
|
||||||
<template #header>
|
|
||||||
<span v-if="!record || !record.id || record.id < 1">{{ t('common.new') }}</span><span v-else>{{
|
|
||||||
t('common.edit') }}</span><span>{{ t('records._') }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<NForm :model="record" inline :rules="rules">
|
|
||||||
<NFormItem :label="t('records.recordType')">
|
|
||||||
<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" />
|
|
||||||
</NFormItem>
|
|
||||||
<NFormItem label="TTL" path="ttl">
|
|
||||||
<NInputNumber v-model:value="record.ttl" :show-button="false">
|
|
||||||
<template #suffix>
|
|
||||||
{{ t('common.unitForSecond') }}
|
|
||||||
</template>
|
|
||||||
</NInputNumber>
|
|
||||||
</NFormItem>
|
|
||||||
</NForm>
|
|
||||||
<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"
|
|
||||||
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"
|
|
||||||
path="host">
|
|
||||||
<NInput v-model:value="(record.content as CNAMERecord | NSRecord).host"
|
|
||||||
:placeholder="t('records.form.host')" />
|
|
||||||
</NFormItem>
|
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
|
|
||||||
<!-- 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%;"
|
|
||||||
:show-button="false" />
|
|
||||||
</NInputGroup>
|
|
||||||
</NFormItem>
|
|
||||||
|
|
||||||
<!-- 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: 15%;"
|
|
||||||
:show-button="false" />
|
|
||||||
<NInputNumber :placeholder="t('records.form.weight')"
|
|
||||||
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: 15%;" :min="0" :max="65535"
|
|
||||||
:show-button="false" />
|
|
||||||
<NInput :placeholder="t('records.form.target')"
|
|
||||||
v-model:value="(record.content as SRVRecord).target" style="width: 55%;" />
|
|
||||||
</NInputGroup>
|
|
||||||
</NFormItem>
|
|
||||||
|
|
||||||
<!-- 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%;"
|
|
||||||
: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')"
|
|
||||||
v-model:value="(record.content as CAARecord).value" style="width: 40%;" />
|
|
||||||
</NInputGroup>
|
|
||||||
</NFormItem>
|
|
||||||
</NForm>
|
|
||||||
|
|
||||||
<template #action>
|
|
||||||
<NFlex justify="end">
|
|
||||||
<NButton size="small" @click="show = false">
|
|
||||||
<template #icon>
|
|
||||||
<NIcon>
|
|
||||||
<Times />
|
|
||||||
</NIcon>
|
|
||||||
</template>
|
|
||||||
{{ t('common.cancel') }}
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<NButton size="small" type="primary" :loading="loading" :disabled="invalidData !== (validationFlags.content | validationFlags.name)" @click="confirm" attr-type="submit">
|
|
||||||
<template #icon>
|
|
||||||
<NIcon>
|
|
||||||
<Check />
|
|
||||||
</NIcon>
|
|
||||||
</template>
|
|
||||||
{{ t('common.confirm') }}
|
|
||||||
</NButton>
|
|
||||||
</NFlex>
|
|
||||||
</template>
|
|
||||||
</NCard>
|
|
||||||
</NModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
NModal,
|
|
||||||
NCard,
|
|
||||||
NForm,
|
|
||||||
NFormItem,
|
|
||||||
NFlex,
|
|
||||||
NButton,
|
|
||||||
NInput,
|
|
||||||
NInputNumber,
|
|
||||||
NInputGroup,
|
|
||||||
NSelect,
|
|
||||||
NIcon,
|
|
||||||
useNotification,
|
|
||||||
type FormRules,
|
|
||||||
type SelectOption,
|
|
||||||
type FormItemRule,
|
|
||||||
} from 'naive-ui'
|
|
||||||
import { getErrorInfo } from '@/apis/api';
|
|
||||||
import {
|
|
||||||
useRecordStore,
|
|
||||||
RecordTypes,
|
|
||||||
type Record,
|
|
||||||
type ARecord,
|
|
||||||
type AAAARecord,
|
|
||||||
type CAARecord,
|
|
||||||
type CNAMERecord,
|
|
||||||
type NSRecord,
|
|
||||||
type SRVRecord,
|
|
||||||
type TXTRecord,
|
|
||||||
type MXRecord,
|
|
||||||
type RecordT,
|
|
||||||
} from '@/stores/records';
|
|
||||||
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()
|
|
||||||
const recordStore = useRecordStore()
|
|
||||||
const recordTypeOptions = Object.entries(RecordTypes).filter(
|
|
||||||
e => e[1] !== RecordTypes.RecordTypeSOA
|
|
||||||
).map(e => {
|
|
||||||
return {
|
|
||||||
label: e[1],
|
|
||||||
value: e[1]
|
|
||||||
} as SelectOption
|
|
||||||
})
|
|
||||||
const rules = {
|
|
||||||
name: {
|
|
||||||
trigger: 'blur',
|
|
||||||
validator() {
|
|
||||||
invalidData.value |= validationFlags.name
|
|
||||||
if (!props.record.name || props.record.name === '') {
|
|
||||||
invalidData.value &= ~validationFlags.name
|
|
||||||
return new Error(t('common.mandatory'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.record.name.includes(' ')) {
|
|
||||||
invalidData.value &= ~validationFlags.name
|
|
||||||
return new Error(t('records.errors.hasSpace'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.record.name.startsWith('.') || props.record.name.endsWith('.')) {
|
|
||||||
invalidData.value &= ~validationFlags.name
|
|
||||||
return new Error(t('records.errors.badName.dotAndMinus'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.record.name.startsWith('-') || props.record.name.endsWith('-')) {
|
|
||||||
invalidData.value &= ~validationFlags.name
|
|
||||||
return new Error(t('records.errors.badName.dotAndMinus'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.record.name.includes('..')) {
|
|
||||||
invalidData.value &= ~validationFlags.name
|
|
||||||
return new Error(t('records.errors.badName.doubleDots'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.record.name.split('.').filter(e => e.length > 63).length > 0) {
|
|
||||||
invalidData.value &= ~validationFlags.name
|
|
||||||
return new Error(t('records.errors.badName.longerThan63'))
|
|
||||||
}
|
|
||||||
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.includes(' ')) {
|
|
||||||
invalidData.value &= ~validationFlags.content
|
|
||||||
return new Error(t('records.errors.hasSpace'))
|
|
||||||
}
|
|
||||||
|
|
||||||
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.includes(' ')) {
|
|
||||||
invalidData.value &= ~validationFlags.content
|
|
||||||
return new Error(t('records.errors.hasSpace'))
|
|
||||||
}
|
|
||||||
|
|
||||||
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.includes(' ')) {
|
|
||||||
invalidData.value &= ~validationFlags.content
|
|
||||||
return new Error(t('records.errors.hasSpace'))
|
|
||||||
}
|
|
||||||
|
|
||||||
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'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r.tag.includes(' ')) {
|
|
||||||
invalidData.value &= ~validationFlags.content
|
|
||||||
return new Error(t('records.errors.hasSpace'))
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as FormRules
|
|
||||||
|
|
||||||
function clearRecordContent() {
|
|
||||||
props.record.content = {} as RecordT
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirm() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
if (!props.record.id || props.record.id < 1) {
|
|
||||||
await recordStore.addRecord(props.domain, props.record)
|
|
||||||
} else {
|
|
||||||
await recordStore.updateRecord(props.domain, props.record)
|
|
||||||
}
|
|
||||||
emit('reload-records')
|
|
||||||
show.value = false
|
|
||||||
} catch (e) {
|
|
||||||
const msg = getErrorInfo(e)
|
|
||||||
notification.error(msg)
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -3,7 +3,7 @@ import './assets/main.css'
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import i18n from './locale/i18n'
|
import i18n from './locale/i18n'
|
||||||
|
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
redirect: '/domains'
|
||||||
component: HomeView
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/domains',
|
path: '/domains',
|
||||||
name: 'domains',
|
name: 'domains',
|
||||||
component: () => import('../views/DomainsView.vue')
|
meta: {
|
||||||
|
type: 'domains'
|
||||||
|
},
|
||||||
|
component: () => import('@/views/DomainsView')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/records/:domain',
|
path: '/records/:domain',
|
||||||
name: 'records',
|
name: 'records',
|
||||||
component: () => import('../views/RecordsView.vue'),
|
meta: {
|
||||||
|
type: 'records'
|
||||||
|
},
|
||||||
|
component: () => import('@/views/RecordsView'),
|
||||||
props: true
|
props: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
3
web/src/views/DomainsView.css
Normal file
3
web/src/views/DomainsView.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.n-card {
|
||||||
|
width: 32vw;
|
||||||
|
}
|
87
web/src/views/DomainsView.tsx
Normal file
87
web/src/views/DomainsView.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import './DomainsView.css'
|
||||||
|
|
||||||
|
import { NSpin, NFlex, NCard, NButton, NIcon, NModalProvider, createDiscreteApi } from 'naive-ui'
|
||||||
|
import { PlusSquare } from "@vicons/fa"
|
||||||
|
import { type Domain, useDomainStore } from '@/stores/domains'
|
||||||
|
import { getErrorInfo } from '@/apis/api'
|
||||||
|
import DomainInfo from '@/components/domains/DomainInfo'
|
||||||
|
import DomainOps from '@/components/domains/DomainOps'
|
||||||
|
import DomainRemoveModal from '@/components/domains/DomainRemoveModal'
|
||||||
|
import DomainEditModal from '@/components/domains/DomainEditModal'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const domainStore = useDomainStore()
|
||||||
|
const { notification } = createDiscreteApi(['notification'])
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const removeModalShow = ref(false);
|
||||||
|
const editModalShow = ref(false);
|
||||||
|
const operationDomain = ref({} as Domain)
|
||||||
|
|
||||||
|
function showRemoveModal(domain: Domain) {
|
||||||
|
operationDomain.value = domain
|
||||||
|
removeModalShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditModal(domain: Domain) {
|
||||||
|
operationDomain.value = domain
|
||||||
|
editModalShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDomain() {
|
||||||
|
const domain = {
|
||||||
|
refresh_interval: 86400,
|
||||||
|
retry_interval: 7200,
|
||||||
|
expiry_period: 3600000,
|
||||||
|
negative_ttl: 86400,
|
||||||
|
serial_number: 1,
|
||||||
|
} as Domain
|
||||||
|
showEditModal(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DomainsView() {
|
||||||
|
try {
|
||||||
|
domainStore.loadDomains()
|
||||||
|
loading.value = false
|
||||||
|
} catch (e) {
|
||||||
|
const msg = getErrorInfo(e)
|
||||||
|
notification.error(msg)
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
loading.value ? <NSpin size="large" /> :
|
||||||
|
<NModalProvider>
|
||||||
|
<NFlex vertical>
|
||||||
|
{
|
||||||
|
domainStore.domains.map((domain: Domain) => (
|
||||||
|
<NCard title={domain.domain_name} key={domain.id} size='large' hoverable>
|
||||||
|
{{
|
||||||
|
default: () => <DomainInfo domain={domain} />,
|
||||||
|
action: () => <DomainOps domain={domain} onRemoveDomain={showRemoveModal} onEditDomain={showEditModal} />
|
||||||
|
}}
|
||||||
|
|
||||||
|
</NCard>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
<NCard hoverable>
|
||||||
|
<NButton block quaternary size="large" onClick={addDomain}>
|
||||||
|
{{
|
||||||
|
icon: () => <NIcon component={PlusSquare} depth={5} />
|
||||||
|
}}
|
||||||
|
</NButton>
|
||||||
|
</NCard>
|
||||||
|
</NFlex>
|
||||||
|
<DomainRemoveModal show={removeModalShow.value} domain={operationDomain.value} onUpdate:show={(v: boolean) => removeModalShow.value = v} />
|
||||||
|
<DomainEditModal show={editModalShow.value} domain={operationDomain.value} onUpdate:show={(v: boolean) => editModalShow.value = v} />
|
||||||
|
</NModalProvider>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DomainsView.displayName = 'DomainsView'
|
||||||
|
|
||||||
|
export default DomainsView
|
@ -1,83 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NSpin, NFlex, NCard, NButton, NIcon, useNotification, NModalProvider } from 'naive-ui'
|
|
||||||
import { PlusSquare } from "@vicons/fa"
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { type Domain, useDomainStore } from '@/stores/domains'
|
|
||||||
import { getErrorInfo } from '@/apis/api'
|
|
||||||
import DomainInfo from '@/components/domains/DomainInfo'
|
|
||||||
import DomainOps from '@/components/domains/DomainOps'
|
|
||||||
import DomainRemoveModal from '@/components/domains/DomainRemoveModal'
|
|
||||||
import DomainEditModal from '@/components/domains/DomainEditModal'
|
|
||||||
|
|
||||||
const domainStore = useDomainStore()
|
|
||||||
const notification = useNotification()
|
|
||||||
|
|
||||||
const loading = ref(true);
|
|
||||||
const removeModalShow = ref(false);
|
|
||||||
const editModalShow = ref(false);
|
|
||||||
const operationDomain = ref({} as Domain)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
try {
|
|
||||||
domainStore.loadDomains()
|
|
||||||
loading.value = false
|
|
||||||
} catch (error) {
|
|
||||||
const msg = getErrorInfo(error)
|
|
||||||
notification.error(msg)
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function showRemoveModal(domain: Domain) {
|
|
||||||
operationDomain.value = domain
|
|
||||||
removeModalShow.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function showEditModal(domain: Domain) {
|
|
||||||
operationDomain.value = domain
|
|
||||||
editModalShow.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDomain() {
|
|
||||||
const domain = {
|
|
||||||
refresh_interval: 86400,
|
|
||||||
retry_interval: 7200,
|
|
||||||
expiry_period: 3600000,
|
|
||||||
negative_ttl: 86400,
|
|
||||||
serial_number: 1,
|
|
||||||
} as Domain
|
|
||||||
showEditModal(domain)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<NSpin size="large" v-if="loading" />
|
|
||||||
<NModalProvider v-else>
|
|
||||||
<NFlex id="domains" vertical>
|
|
||||||
<NCard v-for="domain in domainStore.domains" :title="domain.domain_name" v-bind:key="domain.id"
|
|
||||||
size="large" hoverable>
|
|
||||||
<DomainInfo :domain="domain" />
|
|
||||||
<template #action>
|
|
||||||
<DomainOps :domain="domain" @remove-domain="showRemoveModal" @edit-domain="showEditModal" />
|
|
||||||
</template>
|
|
||||||
</NCard>
|
|
||||||
<NCard hoverable>
|
|
||||||
<NButton block quaternary size="large" @click="addDomain">
|
|
||||||
<template #icon>
|
|
||||||
<NIcon :component="PlusSquare" :depth="5" />
|
|
||||||
</template>
|
|
||||||
</NButton>
|
|
||||||
</NCard>
|
|
||||||
</NFlex>
|
|
||||||
<DomainRemoveModal v-model:show="removeModalShow" :domain="operationDomain" />
|
|
||||||
<DomainEditModal v-model:show="editModalShow" :domain="operationDomain" />
|
|
||||||
</NModalProvider>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.n-card {
|
|
||||||
width: 32vw;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,6 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import router from '@/router';
|
|
||||||
router.push("/domains")
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template></template>
|
|
7
web/src/views/RecordsView.css
Normal file
7
web/src/views/RecordsView.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
div#records {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
250
web/src/views/RecordsView.tsx
Normal file
250
web/src/views/RecordsView.tsx
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import './RecordsView.css'
|
||||||
|
|
||||||
|
import {
|
||||||
|
NSpin, NPageHeader,
|
||||||
|
NFlex, NButton, NIcon, NGrid, NGi,
|
||||||
|
NStatistic, NDataTable, NInput,
|
||||||
|
NModalProvider,
|
||||||
|
createDiscreteApi
|
||||||
|
} from 'naive-ui'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
import { useRecordStore, type Record, type SOARecord, RecordTypes, type ARecord } from '@/stores/records'
|
||||||
|
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'
|
||||||
|
import RecordEditModal from '@/components/records/RecordEditModal'
|
||||||
|
import i18n from '@/locale/i18n'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const { t } = i18n.global
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
domain: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordStore = useRecordStore()
|
||||||
|
const { notification } = createDiscreteApi(['notification'])
|
||||||
|
const editModalShow = ref(false)
|
||||||
|
const editingRecord = ref<Record>({} as Record)
|
||||||
|
const loading = ref(true);
|
||||||
|
const records = ref<Record[] | undefined>([] as Record[]);
|
||||||
|
const soa = ref<SOARecord>({} as SOARecord)
|
||||||
|
const reloadRecords = () => records.value = recordStore.records?.filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||||
|
|
||||||
|
async function refreshRecords(domain: string) {
|
||||||
|
try {
|
||||||
|
await recordStore.loadRecords(domain)
|
||||||
|
reloadRecords()
|
||||||
|
soa.value = recordStore.records?.find(e => e.record_type === RecordTypes.RecordTypeSOA)?.content as SOARecord
|
||||||
|
} catch (err) {
|
||||||
|
const msg = getErrorInfo(err)
|
||||||
|
notification.error(msg)
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goBack() {
|
||||||
|
router.push('/domains')
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRecord(value: string) {
|
||||||
|
if (value.length > 0) {
|
||||||
|
records.value = recordStore.records?.
|
||||||
|
filter(e => e.record_type !== RecordTypes.RecordTypeSOA).
|
||||||
|
filter(e => !!~e.name.indexOf(value))
|
||||||
|
} else {
|
||||||
|
records.value = recordStore.records?.
|
||||||
|
filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRecord(domain: string, record: Record) {
|
||||||
|
try {
|
||||||
|
await recordStore.removeRecord(domain, record)
|
||||||
|
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(domain: string) {
|
||||||
|
showEditing(domain, {
|
||||||
|
zone: `${domain}.`,
|
||||||
|
ttl: 500,
|
||||||
|
record_type: RecordTypes.RecordTypeA,
|
||||||
|
content: {
|
||||||
|
ip: ''
|
||||||
|
} as ARecord
|
||||||
|
} as Record)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateColumns = (domain: string) => [
|
||||||
|
{
|
||||||
|
key: 'no',
|
||||||
|
title: '#',
|
||||||
|
render(_, index) {
|
||||||
|
return index + 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
title: t("records.name"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'record_type',
|
||||||
|
title: t('records.recordType')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'content',
|
||||||
|
title: t('records.content'),
|
||||||
|
render(row: Record) {
|
||||||
|
return Object.entries(row.content).map(i => i[1]).join(" ")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ttl',
|
||||||
|
title: 'TTL (s)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
render(row: Record) {
|
||||||
|
return <RecordOps record={row} domain={domain} onRecord-delete={deleteRecord} onEdit-record={showEditing} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as DataTableColumns<Record>
|
||||||
|
|
||||||
|
const statRefresh = () => (
|
||||||
|
<NGi>
|
||||||
|
<NStatistic value={soa.value.refresh}>
|
||||||
|
{{
|
||||||
|
suffix: () => 's',
|
||||||
|
label: () => (
|
||||||
|
<>
|
||||||
|
<NIcon component={RedoAlt} style={{ transform: 'translateY(2px)' }} />
|
||||||
|
<span>{t('records.refresh')}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
)
|
||||||
|
|
||||||
|
const statRetry = () => (
|
||||||
|
<NGi>
|
||||||
|
<NStatistic value={soa.value.retry}>
|
||||||
|
{{
|
||||||
|
suffix: () => 's',
|
||||||
|
label: () => (
|
||||||
|
<>
|
||||||
|
<NIcon component={CheckCircle} style={{ transform: 'translateY(2px)' }} />
|
||||||
|
<span>{t('records.retry')}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
)
|
||||||
|
|
||||||
|
const statExpire = () => (
|
||||||
|
<NGi>
|
||||||
|
<NStatistic value={soa.value.expire}>
|
||||||
|
{{
|
||||||
|
suffix: () => 's',
|
||||||
|
label: () => (
|
||||||
|
<>
|
||||||
|
<NIcon component={Clock} style={{ transform: 'translateY(2px)' }} />
|
||||||
|
<span>{t('records.expire')}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
)
|
||||||
|
|
||||||
|
const statTTL = () => (
|
||||||
|
<NGi>
|
||||||
|
<NStatistic value={soa.value.minttl}>
|
||||||
|
{{
|
||||||
|
suffix: () => 's',
|
||||||
|
label: () => (
|
||||||
|
<>
|
||||||
|
<NIcon component={Cogs} style={{ transform: 'translateY(2px)' }} />
|
||||||
|
<span>{t('records.ttl')}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NStatistic>
|
||||||
|
</NGi>
|
||||||
|
)
|
||||||
|
|
||||||
|
function recordsViewBodyHeaderExtra() {
|
||||||
|
return (
|
||||||
|
<NGrid cols={4} >
|
||||||
|
<statRefresh />
|
||||||
|
<statRetry />
|
||||||
|
<statExpire />
|
||||||
|
<statTTL />
|
||||||
|
</NGrid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordsViewBody({ domain }: Props) {
|
||||||
|
const columns = generateColumns(domain)
|
||||||
|
return (
|
||||||
|
<NModalProvider>
|
||||||
|
<NPageHeader title={t('domains.dnsRecord')} subtitle={domain} onBack={goBack}>
|
||||||
|
{{
|
||||||
|
extra: () => (
|
||||||
|
<NFlex wrap={false} justify="end" inline>
|
||||||
|
<NButton type="primary" onClick={() => newRecord(domain)}>
|
||||||
|
{{
|
||||||
|
icon: () => <NIcon component={PlusSquare} />,
|
||||||
|
default: () => t('common.add')
|
||||||
|
}}
|
||||||
|
</NButton>
|
||||||
|
<NInput placeholder={t('records.search')} onUpdate:value={searchRecord} clearable>
|
||||||
|
{{
|
||||||
|
prefix: () => <NIcon component={Search} />
|
||||||
|
}}
|
||||||
|
</NInput>
|
||||||
|
</NFlex>
|
||||||
|
),
|
||||||
|
default: () => <recordsViewBodyHeaderExtra />
|
||||||
|
}}
|
||||||
|
</NPageHeader>
|
||||||
|
<br />
|
||||||
|
<NDataTable data={records.value} columns={columns} pagination={{ pageSize: 20 }} />
|
||||||
|
</NModalProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RecordsView({ domain }: Props) {
|
||||||
|
try {
|
||||||
|
refreshRecords(domain)
|
||||||
|
} catch (err) {
|
||||||
|
const msg = getErrorInfo(err)
|
||||||
|
notification.error(msg)
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div id='records'>
|
||||||
|
<RecordEditModal show={editModalShow.value} domain={domain} record={editingRecord.value} onReloadRecords={reloadRecords} />
|
||||||
|
{
|
||||||
|
loading.value ? <NSpin size='large' /> : <recordsViewBody domain={domain} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordsView.displayName = 'RecordsView'
|
||||||
|
export default RecordsView
|
@ -1,231 +0,0 @@
|
|||||||
<script setup lang="tsx">
|
|
||||||
import {
|
|
||||||
NSpin, NPageHeader, useNotification,
|
|
||||||
NFlex, NButton, NIcon, NGrid, NGi,
|
|
||||||
NStatistic, NDataTable, NInput,
|
|
||||||
NModalProvider
|
|
||||||
} from 'naive-ui'
|
|
||||||
import type { DataTableColumns } from 'naive-ui'
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { useRecordStore, type Record, type SOARecord, RecordTypes } from '@/stores/records'
|
|
||||||
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'
|
|
||||||
import RecordEditModal from '@/components/records/RecordEditModal.vue'
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
domain: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
key: 'no',
|
|
||||||
title: '#',
|
|
||||||
render(_, index) {
|
|
||||||
return index + 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'name',
|
|
||||||
title: t("records.name"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'record_type',
|
|
||||||
title: t('records.recordType')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'content',
|
|
||||||
title: t('records.content'),
|
|
||||||
render(row: Record) {
|
|
||||||
return Object.entries(row.content).map(i => i[1]).join(" ")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'ttl',
|
|
||||||
title: 'TTL (s)'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
render(row: Record) {
|
|
||||||
return <RecordOps record={row} domain={props.domain} onRecord-delete={deleteRecord} onEdit-record={showEditing} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
] as DataTableColumns<Record>
|
|
||||||
|
|
||||||
const recordStore = useRecordStore()
|
|
||||||
const notification = useNotification()
|
|
||||||
|
|
||||||
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 {
|
|
||||||
refreshRecords()
|
|
||||||
} catch (err) {
|
|
||||||
const msg = getErrorInfo(err)
|
|
||||||
notification.error(msg)
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const reloadRecords = () => records.value = recordStore.records?.filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
|
||||||
|
|
||||||
|
|
||||||
async function refreshRecords() {
|
|
||||||
try {
|
|
||||||
await recordStore.loadRecords(props.domain)
|
|
||||||
reloadRecords()
|
|
||||||
soa.value = recordStore.records?.find(e => e.record_type === RecordTypes.RecordTypeSOA)?.content as SOARecord
|
|
||||||
} catch (err) {
|
|
||||||
const msg = getErrorInfo(err)
|
|
||||||
notification.error(msg)
|
|
||||||
console.error(err)
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goBack() {
|
|
||||||
router.push('/domains')
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchRecord(value: string) {
|
|
||||||
if (value.length > 0) {
|
|
||||||
records.value = recordStore.records?.
|
|
||||||
filter(e => e.record_type !== RecordTypes.RecordTypeSOA).
|
|
||||||
filter(e => !!~e.name.indexOf(value))
|
|
||||||
} else {
|
|
||||||
records.value = recordStore.records?.
|
|
||||||
filter(e => e.record_type !== RecordTypes.RecordTypeSOA)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteRecord(domain: string, record: Record) {
|
|
||||||
try {
|
|
||||||
await recordStore.removeRecord(domain, record)
|
|
||||||
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" @click="newRecord">
|
|
||||||
<template #icon>
|
|
||||||
<NIcon>
|
|
||||||
<PlusSquare />
|
|
||||||
</NIcon>
|
|
||||||
</template>
|
|
||||||
{{ t('common.add') }}
|
|
||||||
</NButton>
|
|
||||||
<NInput :placeholder="t('records.search')" @update:value="searchRecord" clearable>
|
|
||||||
<template #prefix>
|
|
||||||
<NIcon :component="Search" />
|
|
||||||
</template>
|
|
||||||
</NInput>
|
|
||||||
</NFlex>
|
|
||||||
</template>
|
|
||||||
<NGrid :cols="4">
|
|
||||||
<NGi>
|
|
||||||
<NStatistic :value="soa?.refresh">
|
|
||||||
<template #suffix>
|
|
||||||
s
|
|
||||||
</template>
|
|
||||||
<template #label>
|
|
||||||
<NIcon class="icon">
|
|
||||||
<RedoAlt />
|
|
||||||
</NIcon>
|
|
||||||
{{ t('records.refresh') }}
|
|
||||||
</template>
|
|
||||||
</NStatistic>
|
|
||||||
</NGi>
|
|
||||||
<NGi>
|
|
||||||
<NStatistic :value="soa?.retry">
|
|
||||||
<template #suffix>
|
|
||||||
s
|
|
||||||
</template>
|
|
||||||
<template #label>
|
|
||||||
<NIcon class="icon">
|
|
||||||
<CheckCircle />
|
|
||||||
</NIcon>
|
|
||||||
{{ t('records.retry') }}
|
|
||||||
</template>
|
|
||||||
</NStatistic>
|
|
||||||
</NGi>
|
|
||||||
<NGi>
|
|
||||||
<NStatistic :value="soa?.expire">
|
|
||||||
<template #suffix>
|
|
||||||
s
|
|
||||||
</template>
|
|
||||||
<template #label>
|
|
||||||
<NIcon class="icon">
|
|
||||||
<Clock />
|
|
||||||
</NIcon>
|
|
||||||
{{ t('records.expire') }}
|
|
||||||
</template>
|
|
||||||
</NStatistic>
|
|
||||||
</NGi>
|
|
||||||
<NGi>
|
|
||||||
<NStatistic :value="soa?.minttl">
|
|
||||||
<template #suffix>
|
|
||||||
s
|
|
||||||
</template>
|
|
||||||
<template #label>
|
|
||||||
<NIcon class="icon">
|
|
||||||
<Cogs />
|
|
||||||
</NIcon>
|
|
||||||
{{ t('records.ttl') }}
|
|
||||||
</template>
|
|
||||||
</NStatistic>
|
|
||||||
</NGi>
|
|
||||||
</NGrid>
|
|
||||||
</NPageHeader>
|
|
||||||
<br />
|
|
||||||
<NDataTable :data="records" :columns="columns" :pagination="{ pageSize: 20 }" />
|
|
||||||
</NModalProvider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.icon {
|
|
||||||
transform: translateY(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
div#records {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -7,5 +7,10 @@
|
|||||||
{
|
{
|
||||||
"path": "./tsconfig.app.json"
|
"path": "./tsconfig.app.json"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxFactory": "h",
|
||||||
|
"jsxFragmentFactory": "Fragment",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user